Chamando o construtor de tipo base em qualquer lugar

Tive uma entrevista recentemente e, entre outras, havia uma pergunta sobre a ordem de chamada dos construtores em C #. Após responder, o entrevistado decidiu demonstrar sua erudição e afirmou que em Java, o construtor de um tipo base pode ser chamado em qualquer lugar do construtor de um tipo derivado, e C #, claro, perde nisso.



A afirmação acabou por ser uma mentira, uma mentira e uma provocação
image



Mas não importava mais, porque o desafio foi aceito.



imagem



aviso Legal
. . . .



Treinamento



Criamos uma cadeia de herança. Para simplificar, usaremos construtores sem parâmetros. No construtor, exibiremos informações sobre o tipo e o identificador do objeto no qual ele é chamado.



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()}");
    }
}


Execute o programa:



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


E obtemos o resultado:



Type 'A' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'C' .ctor called on object #58225482



Digressão lírica
, . , . , . :



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


Removendo código duplicado



Adicione uma classe auxiliar:



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


E nós substituímos em todos os construtores



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


em



this.Trace();


No entanto, o programa agora mostra: Em nosso caso, o seguinte truque pode ser usado. Quem sabe sobre os tipos de tempo de compilação? Compilador. Ele também seleciona sobrecargas de método com base nesses tipos. E para tipos e métodos genéricos, ele também gera entidades construídas. Portanto, retornamos a inferência de tipo correta reescrevendo o método Trace da seguinte maneira:



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()}");


Acessando um construtor de tipo base



É aqui que a reflexão vem em socorro. Adicione um método às extensões :



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


Adicione a propriedade aos tipos B e C :



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


Chamar um construtor de tipo base em qualquer lugar



Altere o conteúdo dos construtores B e C para:



this.Trace();
@base();


Agora, a saída é semelhante a esta:



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



Mudando a ordem de chamada dos construtores do tipo base



Dentro do tipo A, crie um tipo auxiliar:



protected class CtorHelper
{
    private CtorHelper() { }
}


Como apenas a semântica é importante aqui, faz sentido tornar o construtor de tipo privado. A instanciação não faz sentido. O tipo destina-se exclusivamente a distinguir entre as sobrecargas dos construtores do tipo A e aquelas derivadas dele. Pelo mesmo motivo, o tipo deve ser colocado dentro de A e protegido.



Adicione os construtores apropriados para A , B e C :



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


Para os tipos B e C, adicione uma chamada para todos os construtores:



: base(null)


Como resultado, as classes devem ser semelhantes a este
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();
    }
}


E a saída se torna:



Type 'C' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482



O simplório ingênuo pensa que o compilador trapaceou
image



Compreendendo o resultado



Adicionando um método às extensões :



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


e chamando-o em todos os construtores que aceitam CtorHelper , obtemos a seguinte saída: A ordem dos construtores de acordo com o princípio base / derivado, é claro, não mudou. Mesmo assim, a ordem dos construtores disponíveis para o código do cliente que carregam a carga semântica foi alterada graças ao redirecionamento por meio de chamadas para inacessível para os construtores auxiliares do cliente que não fazem nada.



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