multipla - polimorfismo em c#




Chamada de membro virtual em um construtor (12)

Apenas para adicionar meus pensamentos. Se você sempre inicializar o campo privado ao defini-lo, esse problema deve ser evitado. Pelo menos abaixo o código funciona como um encanto:

class Parent
{
    public Parent()
    {
        DoSomething();
    }
    protected virtual void DoSomething()
    {
    }
}

class Child : Parent
{
    private string foo = "HELLO";
    public Child() { /*Originally foo initialized here. Removed.*/ }
    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower());
    }
}

Estou recebendo um aviso do ReSharper sobre uma chamada para um membro virtual do meu construtor de objetos.

Por que isso seria algo a não fazer?


As razões do aviso já estão descritas, mas como você consertaria o aviso? Você tem que selar qualquer classe ou membro virtual.

  class B
  {
    protected virtual void Foo() { }
  }

  class A : B
  {
    public A()
    {
      Foo(); // warning here
    }
  }

Você pode selar a classe A:

  sealed class A : B
  {
    public A()
    {
      Foo(); // no warning
    }
  }

Ou você pode selar o método Foo:

  class A : B
  {
    public A()
    {
      Foo(); // no warning
    }

    protected sealed override void Foo()
    {
      base.Foo();
    }
  }

Cuidado ao seguir cegamente o conselho de Resharper e selar a classe! Se for um modelo no EF Code First, ele removerá a palavra-chave virtual e desativará o carregamento lento de seus relacionamentos.

    public **virtual** User User{ get; set; }

Em C #, o construtor de uma classe base é executado antes do construtor da classe derivada, portanto, quaisquer campos de instância que uma classe derivada possa usar no membro virtual possivelmente substituído ainda não foram inicializados.

Observe que isso é apenas um aviso para fazer com que você preste atenção e verifique se está tudo certo. Existem casos de uso reais para este cenário, você só precisa documentar o comportamento do membro virtual que não pode usar campos de instância declarados em uma classe derivada abaixo de onde o construtor está chamando.


Há respostas bem escritas acima para por que você não gostaria de fazer isso. Aqui está um contra-exemplo onde talvez você queira fazer isso (traduzido em C # de Practical Object-Oriented Design em Ruby por Sandi Metz, p. 126).

Observe que GetDependency() não está tocando em nenhuma variável de instância. Seria estático se os métodos estáticos pudessem ser virtuais.

(Para ser justo, provavelmente existem maneiras mais inteligentes de fazer isso através de containers de injeção de dependência ou inicializadores de objetos ...)

public class MyClass
{
    private IDependency _myDependency;

    public MyClass(IDependency someValue = null)
    {
        _myDependency = someValue ?? GetDependency();
    }

    // If this were static, it could not be overridden
    // as static methods cannot be virtual in C#.
    protected virtual IDependency GetDependency() 
    {
        return new SomeDependency();
    }
}

public class MySubClass : MyClass
{
    protected override IDependency GetDependency()
    {
        return new SomeOtherDependency();
    }
}

public interface IDependency  { }
public class SomeDependency : IDependency { }
public class SomeOtherDependency : IDependency { }

Há uma diferença entre C ++ e C # neste caso específico. Em C ++, o objeto não é inicializado e, portanto, não é seguro chamar uma função virutal dentro de um construtor. Em C # quando um objeto de classe é criado, todos os seus membros são zero inicializados. É possível chamar uma função virtual no construtor, mas se você puder acessar membros que ainda são zero. Se você não precisa acessar membros, é bastante seguro chamar uma função virtual em C #.


Outra coisa interessante que descobri é que o erro ReSharper pode ser 'satisfeito' fazendo algo como abaixo, o que é idiota para mim (no entanto, como mencionado por muitos anteriormente, ainda não é uma boa idéia chamar virtual prop / methods in ctor.

public class ConfigManager
{

   public virtual int MyPropOne { get; private set; }
   public virtual string MyPropTwo { get; private set; }

   public ConfigManager()
   {
    Setup();
   }

   private void Setup()
   {
    MyPropOne = 1;
    MyPropTwo = "test";
   }

}


Porque até que o construtor tenha concluído a execução, o objeto não está totalmente instanciado. Quaisquer membros referenciados pela função virtual podem não ser inicializados. Em C ++, quando você está em um construtor, this se refere apenas ao tipo estático do construtor em que você está, e não ao tipo dinâmico real do objeto que está sendo criado. Isso significa que a chamada de função virtual pode nem chegar onde você espera.


Seu construtor pode (mais tarde, em uma extensão do seu software) ser chamado a partir do construtor de uma subclasse que substitui o método virtual. Agora não a implementação da subclasse da função, mas a implementação da classe base será chamada. Então, não faz sentido chamar uma função virtual aqui.

No entanto, se o seu design satisfizer o princípio da substituição de Liskov, nenhum dano será feito. Provavelmente é por isso que é tolerado - um aviso, não um erro.


Sim, geralmente é ruim chamar o método virtual no construtor.

Neste ponto, o objeto pode não estar totalmente construído ainda, e os invariantes esperados por métodos podem não se sustentar ainda.


Um importante bit ausente é: qual é a maneira correta de resolver esse problema?

Como Greg explicou , o problema raiz aqui é que um construtor de classe base invocaria o membro virtual antes que a classe derivada fosse construída.

O código a seguir, retirado das diretrizes de design do construtor do MSDN , demonstra esse problema.

public class BadBaseClass
{
    protected string state;

    public BadBaseClass()
    {
        this.state = "BadBaseClass";
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBad : BadBaseClass
{
    public DerivedFromBad()
    {
        this.state = "DerivedFromBad";
    }

    public override void DisplayState()
    {   
        Console.WriteLine(this.state);
    }
}

Quando uma nova instância de DerivedFromBad é criada, o construtor da classe base chama o DisplayState e mostra BadBaseClass porque o campo ainda não foi atualizado pelo construtor derivado.

public class Tester
{
    public static void Main()
    {
        var bad = new DerivedFromBad();
    }
}

Uma implementação aprimorada remove o método virtual do construtor da classe base e usa um método Initialize . Criar uma nova instância de DerivedFromBetter exibe o "DerivedFromBetter" esperado

public class BetterBaseClass
{
    protected string state;

    public BetterBaseClass()
    {
        this.state = "BetterBaseClass";
        this.Initialize();
    }

    public void Initialize()
    {
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBetter : BetterBaseClass
{
    public DerivedFromBetter()
    {
        this.state = "DerivedFromBetter";
    }

    public override void DisplayState()
    {
        Console.WriteLine(this.state);
    }
}

Para responder à sua pergunta, considere esta pergunta: o que o código abaixo imprimirá quando o objeto Child for instanciado?

class Parent
{
    public Parent()
    {
        DoSomething();
    }

    protected virtual void DoSomething() 
    {
    }
}

class Child : Parent
{
    private string foo;

    public Child() 
    { 
        foo = "HELLO"; 
    }

    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower()); //NullReferenceException!?!
    }
}

A resposta é que, de fato, um NullReferenceException será lançado, porque foo é nulo. O construtor base de um objeto é chamado antes de seu próprio construtor . Ao ter uma chamada virtual no construtor de um objeto, você está introduzindo a possibilidade de que objetos herdados executem o código antes de serem totalmente inicializados.





virtual-functions