c# polimorfismo Chamada de membro virtual em um construtor




polimorfismo em c# (14)

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; }

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?


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

}


Gostaria apenas de adicionar um método Initialize () à classe base e, em seguida, chamar isso de construtores derivados. Esse método irá chamar qualquer método virtual / abstrato / propriedades após todos os construtores foram executados :)


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.


As regras do C # são muito diferentes das do Java e C ++.

Quando você está no construtor para algum objeto em C #, esse objeto existe em um formulário totalmente inicializado (não apenas "construído"), como seu tipo totalmente derivado.

namespace Demo
{
    class A 
    {
      public A()
      {
        System.Console.WriteLine("This is a {0},", this.GetType());
      }
    }

    class B : A
    {      
    }

    // . . .

    B b = new B(); // Output: "This is a Demo.B"
}

Isso significa que, se você chamar uma função virtual do construtor de A, ela será resolvida para qualquer substituição em B, se uma for fornecida.

Mesmo que você intencionalmente configure A e B assim, entendendo completamente o comportamento do sistema, você poderá ter um choque mais tarde. Digamos que você chamou funções virtuais no construtor de B, "sabendo" que elas seriam manipuladas por B ou A, conforme apropriado. Então o tempo passa e outra pessoa decide que precisa definir C e substituir algumas das funções virtuais. De repente, o construtor de B acaba chamando o código em C, o que pode levar a um comportamento bastante surpreendente.

Provavelmente, é uma boa idéia evitar funções virtuais em construtores, já que as regras são tão diferentes entre C #, C ++ e Java. Seus programadores podem não saber o que esperar!


Quando um objeto escrito em C # é construído, o que acontece é que os inicializadores são executados em ordem da classe mais derivada para a classe base e, em seguida, construtores são executados em ordem da classe base para a classe mais derivada ( consulte o blog de Eric Lippert para detalhes por que isso acontece ).

Também em objetos .NET não mudam de tipo à medida que são construídos, mas começam como o tipo mais derivado, com a tabela de método sendo para o tipo mais derivado. Isso significa que as chamadas de método virtual sempre são executadas no tipo mais derivado.

Quando você combina esses dois fatos, fica com o problema de que, se fizer uma chamada de método virtual em um construtor, e não for o tipo mais derivado em sua hierarquia de herança, ela será chamada em uma classe cujo construtor não foi executado e, portanto, pode não estar em um estado adequado para ter esse método chamado.

Este problema é, naturalmente, mitigado se você marcar sua classe como selada para garantir que seja o tipo mais derivado na hierarquia de herança - nesse caso, é perfeitamente seguro chamar o método virtual.


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

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.


O aviso é um lembrete de que membros virtuais provavelmente serão substituídos na classe derivada. Nesse caso, o que quer que a classe pai tenha feito com um membro virtual será desfeito ou alterado pela substituição da classe filha. Olhe para o pequeno golpe de exemplo para maior clareza

A classe pai abaixo tenta definir o valor para um membro virtual em seu construtor. E isso acionará o aviso Re-sharper, deixe ver no código:

public class Parent
{
    public virtual object Obj{get;set;}
    public Parent()
    {
        // Re-sharper warning: this is open to change from 
        // inheriting class overriding virtual member
        this.Obj = new Object();
    }
}

A classe filha aqui substitui a propriedade pai. Se esta propriedade não estiver marcada como virtual, o compilador avisará que a propriedade oculta a propriedade na classe pai e sugerirá que você adicione a palavra-chave 'new' se for intencional.

public class Child: Parent
{
    public Child():base()
    {
        this.Obj = "Something";
    }
    public override object Obj{get;set;}
}

Finalmente, o impacto sobre o uso, a saída do exemplo abaixo abandona o valor inicial definido pelo construtor da classe pai. E é isso que o Re-sharper tenta avisá-lo , os valores definidos no construtor da classe Parent estão abertos para serem sobrescritos pelo construtor da classe filha, que é chamado logo após o construtor da classe pai .

public class Program
{
    public static void Main()
    {
        var child = new Child();
        // anything that is done on parent virtual member is destroyed
        Console.WriteLine(child.Obj);
        // Output: "Something"
    }
} 

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

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 #.


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




virtual-functions