Uso de palavra-chave virtual + substituição vs. nova

Quais são as diferenças entre declarar um método em um tipo base ” virtual ” e depois substituí-lo em um tipo filho usando a palavra-chave ” override ” em vez de simplesmente usar a palavra-chave ” new ” ao declarar o método correspondente no tipo filho?

A “nova” palavra-chave não sobrescreve, significa um novo método que não tem nada a ver com o método da class base.

 public class Foo { public bool DoSomething() { return false; } } public class Bar : Foo { public new bool DoSomething() { return true; } } public class Test { public static void Main () { Foo test = new Bar (); Console.WriteLine (test.DoSomething ()); } } 

Isso imprime falso, se você usou replace teria impresso verdadeiro.

(Código de base retirado de Joseph Daigle)

Então, se você está fazendo polymorphism real, você deve sempre se sobrepor . O único lugar onde você precisa usar “novo” é quando o método não está relacionado de alguma forma à versão da class base.

Eu sempre acho coisas assim mais facilmente entendidas com imagens:

Mais uma vez, tomando o código de Joseph Daigle,

 public class Foo { public /*virtual*/ bool DoSomething() { return false; } } public class Bar : Foo { public /*override or new*/ bool DoSomething() { return true; } } 

Se você, então, chamar o código assim:

 Foo a = new Bar(); a.DoSomething(); 

NOTA: O importante é que nosso object é na verdade um Bar , mas nós o estamos armazenando em uma variável do tipo Foo (isso é semelhante a lançá-lo)

Em seguida, o resultado será o seguinte, dependendo se você usou virtual / override ou new ao declarar suas classs.

Explicação Virtual / Override

Aqui está um código para entender a diferença no comportamento de methods virtuais e não virtuais:

 class A { public void foo() { Console.WriteLine("A::foo()"); } public virtual void bar() { Console.WriteLine("A::bar()"); } } class B : A { public new void foo() { Console.WriteLine("B::foo()"); } public override void bar() { Console.WriteLine("B::bar()"); } } class Program { static int Main(string[] args) { B b = new B(); A a = b; a.foo(); // Prints A::foo b.foo(); // Prints B::foo a.bar(); // Prints B::bar b.bar(); // Prints B::bar return 0; } } 

A new palavra-chave, na verdade, cria um membro completamente novo que existe apenas nesse tipo específico.

Por exemplo

 public class Foo { public bool DoSomething() { return false; } } public class Bar : Foo { public new bool DoSomething() { return true; } } 

O método existe em ambos os tipos. Quando você usa reflection e obtém os membros do tipo Bar , você encontrará dois methods chamados DoSomething() que parecem exatamente os mesmos. Usando new você efetivamente esconde a implementação na class base, de forma que quando as classs derivam de Bar (no meu exemplo) a chamada de método para base.DoSomething() vai para Bar e não para Foo .

virtual / override informa ao compilador que os dois methods estão relacionados e que, em algumas circunstâncias, quando você pensa estar chamando o primeiro método (virtual), é realmente correto chamar o segundo método (substituído). Esta é a base do polymorphism.

 (new SubClass() as BaseClass).VirtualFoo() 

Vai chamar o método VirtualFoo () da subclass.

new informa ao compilador que você está adicionando um método a uma class derivada com o mesmo nome de um método na class base, mas eles não têm nenhum relacionamento um com o outro.

 (new SubClass() as BaseClass).NewBar() 

Vai chamar o método NewBar () da BaseClass, enquanto:

 (new SubClass()).NewBar() 

Chamará o método NewBar () da subclass.

Além dos detalhes técnicos, acho que o uso de virtual / override comunica muitas informações semânticas no design. Quando você declara um método virtual, indica que espera que as classs de implementação possam fornecer suas próprias implementações não padrão. Omitir isso em uma class base, da mesma forma, declara a expectativa de que o método padrão deve ser suficiente para todas as classs de implementação. Da mesma forma, pode-se usar declarações abstratas para forçar a implementação de classs para fornecer sua própria implementação. Novamente, acho que isso comunica muito sobre como o programador espera que o código seja usado. Se eu estivesse escrevendo as classs base e implementando e me encontrasse usando o novo, seriamente repensar a decisão de não tornar o método virtual no pai e declarar minha intenção especificamente.

A diferença entre a palavra-chave override e a nova palavra-chave é que a primeira substitui o método e a última oculta o método.

Confira os links abaixo para mais informações …

MSDN e outros

  • new palavra-chave é para esconder. – significa que você está escondendo seu método em tempo de execução. A saída será baseada no método da class base.
  • override por override . – significa que você está invocando seu método de class derivada com a referência da class base. A saída será baseada no método da class derivada.

Minha versão da explicação vem do uso de propriedades para ajudar a entender as diferenças.

override é bastante simples, certo? O tipo subjacente substitui o pai.

new é talvez o enganador (para mim foi). Com propriedades, é mais fácil de entender:

 public class Foo { public bool GetSomething => false; } public class Bar : Foo { public new bool GetSomething => true; } public static void Main(string[] args) { Foo foo = new Bar(); Console.WriteLine(foo.GetSomething); Bar bar = new Bar(); Console.WriteLine(bar.GetSomething); } 

Usando um depurador, você pode notar que o Foo foo tem 2 propriedades GetSomething , já que na verdade tem 2 versões da propriedade, Foo e Bar , e para saber qual usar, c # “escolhe” a propriedade para o tipo atual .

Se você quisesse usar a versão do Bar, teria usado replace ou usar Foo foo .

Bar bar tem apenas 1 , como quer completamente novo comportamento para GetSomething .