どこでも基本型コンストラクターを呼び出す

最近インタビューをしましたが、C#でコンストラクターを呼び出す順序について質問がありました。回答後、インタビュー対象者は自分の知識を示すことを決定し、Javaでは、基本型のコンストラクターは派生型のコンストラクターのどこからでも呼び出すことができ、C#はもちろんこれで負けると述べました。



声明は嘘、嘘、挑発であることが判明しました
image



しかし、挑戦が受け入れられたので、それはもはや問題ではありませんでした。



画像



免責事項
. . . .



トレーニング



継承チェーンを作成します。簡単にするために、パラメーターなしのコンストラクターを使用します。コンストラクターでは、呼び出されたオブジェクトのタイプとIDに関する情報を表示します。



public class A
{
    public A()
    {
        Console.WriteLine($"Type '{nameof(A)}' .ctor called on object #{GetHashCode()}");
    }
}

public class B : A
{
    public B()
    {
        Console.WriteLine($"Type '{nameof(B)}' .ctor called on object #{GetHashCode()}");
    }
}

public class C : B
{
    public C()
    {
        Console.WriteLine($"Type '{nameof(C)}' .ctor called on object #{GetHashCode()}");
    }
}


プログラムを実行します。



class Program
{
    static void Main()
    {
        new C();
    }
}


そして、出力を取得します。



Type 'A' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'C' .ctor called on object #58225482



叙情的な逸脱
, . , . , . :



public A() : this() { } // CS0516  Constructor 'A.A()' cannot call itself


:



public A() : this(new object()) { }
public A(object _) : this(0) { }
public A(int _) : this() { } // CS0768  Constructor 'A.A(int)' cannot call itself through another constructor


重複するコードの削除



ヘルパークラスを追加します。



internal static class Extensions
{
    public static void Trace(this object obj) =>
        Console.WriteLine($"Type '{obj.GetType().Name}' .ctor called on object #{obj.GetHashCode()}");
}


そして、すべてのコンストラクターで置き換えます



Console.WriteLine($"Type '{nameof(...)}' .ctor called on object #{GetHashCode()}");


オン



this.Trace();


ただし、プログラムは出力します。 この場合、次のトリックを使用できます。コンパイル時のタイプについて誰が知っていますか?コンパイラ。また、これらのタイプに基づいてメソッドのオーバーロードを選択します。また、汎用タイプとメソッドの場合、構築されたエンティティも生成します。したがって、Traceメソッドを次のように書き直すことにより、正しい型推論を返します。



Type 'C' .ctor called on object #58225482

Type 'C' .ctor called on object #58225482

Type 'C' .ctor called on object #58225482







public static void Trace<T>(this T obj) =>
    Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}");


基本型コンストラクターへのアクセス



ここで、反省が助けになります。拡張機能にメソッドを追加します:



public static Action GetBaseConstructor<T>(this T obj) =>
    () => typeof(T)
          .BaseType
          .GetConstructor(Type.EmptyTypes)
          .Invoke(obj, Array.Empty<object>());


タイプBおよびCにプロパティを追加します。



private Action @base => this.GetBaseConstructor();


どこでも基本型コンストラクターを呼び出す



コンストラクターBおよびCの内容を次のように変更します



this.Trace();
@base();


これで、出力は次のようになります。



Type 'A' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482

Type 'C' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482



基本型コンストラクターの呼び出し順序の変更



タイプAの内部で、ヘルパータイプを作成します。



protected class CtorHelper
{
    private CtorHelper() { }
}


ここではセマンティクスのみが重要であるため、型コンストラクターをプライベートにすることは理にかなっています。インスタンス化は意味がありません。このタイプは、タイプAコンストラクターのオーバーロードとそれから派生したオーバーロードを区別することのみを目的としています。同じ理由で、タイプはAの内側に配置し、保護する必要があります適切なコンストラクター



AB、およびCに追加します。



protected A(CtorHelper _) { }
protected B(CtorHelper _) { }
protected C(CtorHelper _) { }


タイプBおよびCの場合、すべてのコンストラクターへの呼び出しを追加します。



: base(null)


結果として、クラスは次のようになります。
internal static class Extensions
{
    public static Action GetBaseConstructor<T>(this T obj) =>
        () => typeof(T)
        .BaseType
        .GetConstructor(Type.EmptyTypes)
        .Invoke(obj, Array.Empty<object>());

    public static void Trace<T>(this T obj) =>
        Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}");
}

public class A
{
    protected A(CtorHelper _) { }

    public A()
    {
        this.Trace();
    }

    protected class CtorHelper
    {
        private CtorHelper() { }
    }
}

public class B : A
{
    private Action @base => this.GetBaseConstructor();

    protected B(CtorHelper _) : base(null) { }

    public B() : base(null)
    {
        this.Trace();
        @base();
    }
}

public class C : B
{
    private Action @base => this.GetBaseConstructor();

    protected C(CtorHelper _) : base(null) { }

    public C() : base(null)
    {
        this.Trace();
        @base();
    }
}


そして、出力は次のようになります。



Type 'C' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482



素朴なシンプトンは、コンパイラがだまされたと考えています
image



結果を理解する



拡張機能にメソッド を追加することにより



public static void TraceSurrogate<T>(this T obj) =>
    Console.WriteLine($"Type '{typeof(T).Name}' surrogate .ctor called on object #{obj.GetHashCode()}");


そして、CtorHelper受け入れるすべてのコンストラクターでそれを呼び出すと 次の出力が得られます。もちろん、基本/派生原理に従ったコンストラクターの順序は変更されていません。しかし、それでも、クライアントがアクセスできないヘルパーコンストラクターへの呼び出しによるリダイレクトのおかげで、セマンティックロードを実行するクライアントコードで使用可能なコンストラクターの順序が変更されました。



Type 'A' surrogate .ctor called on object #58225482

Type 'B' surrogate .ctor called on object #58225482

Type 'C' .ctor called on object #58225482

Type 'A' surrogate .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482






All Articles