Diferença entre novo e substituir


198

Pensando em qual é a diferença entre o seguinte:

Caso 1: Classe Base

public void DoIt();

Caso 1: Classe herdada

public new void DoIt();

Caso 2: Classe Base

public virtual void DoIt();

Caso 2: Classe herdada

public override void DoIt();

Os casos 1 e 2 parecem ter o mesmo efeito com base nos testes que eu executei. Existe uma diferença ou uma maneira preferida?


2
Duplicar de muitas questões, incluindo stackoverflow.com/questions/159978/...
Jon Skeet

Respostas:


267

O modificador de substituição pode ser usado em métodos virtuais e deve ser usado em métodos abstratos. Isso indica para o compilador usar a última implementação definida de um método. Mesmo que o método seja chamado em uma referência à classe base, ele usará a implementação que o substituirá.

public class Base
{
    public virtual void DoIt()
    {
    }
}

public class Derived : Base
{
    public override void DoIt()
    {
    }
}

Base b = new Derived();
b.DoIt();                      // Calls Derived.DoIt

chamará Derived.DoItse isso substituir Base.DoIt.

O novo modificador instrui o compilador a usar sua implementação de classe filho em vez da implementação de classe pai. Qualquer código que não faça referência à sua classe, mas a classe pai usará a implementação da classe pai.

public class Base
{
    public virtual void DoIt()
    {
    }
}

public class Derived : Base
{
    public new void DoIt()
    {
    }
}

Base b = new Derived();
Derived d = new Derived();

b.DoIt();                      // Calls Base.DoIt
d.DoIt();                      // Calls Derived.DoIt

Ligará primeiro Base.DoIt, então Derived.DoIt. Eles são efetivamente dois métodos completamente separados que têm o mesmo nome, em vez do método derivado substituindo o método base.

Fonte: Microsoft blog


5
This indicates for the compiler to use the last defined implementation of a method. como encontrar a última implementação definida de um método?
AminM 5/03/14

5
Comece com uma classe concreta, verifique se há uma implementação do método de interesse. Se isso acontecer, você está pronto. Caso contrário, avance um passo na hierarquia de herança, ou seja, verifique se a superclasse possui o método de interesse. Continue até encontrar o método de interesse.
csoltenborn

2
Observe também que você pode apenas overrideum método quando a classe base define o método como virtual. A palavra virtualé a classe base dizendo "Hey, quando eu chamar esse método, que poderia ter praticamente foi substituído por uma aplicação derivada, então eu realmente não sei de antemão o que implementação do método Na verdade, estou chamando em tempo de execução. Assim virtualsignifica é . um espaço reservado para um método Isto implica que os métodos que não são marcados como virtualnão pode ser substituído Mas você pode. substituir qualquer método não-virtual em uma classe derivada com o modificador new, acessíveis apenas a nível derivada.
Erik Bongers

177

virtual : indica que um método pode ser substituído por um herdeiro

substituir : substitui a funcionalidade de um método virtual em uma classe base, fornecendo funcionalidade diferente.

novo : oculta o método original (que não precisa ser virtual), fornecendo funcionalidades diferentes. Isso só deve ser usado onde for absolutamente necessário.

Quando você oculta um método, você ainda pode acessar o método original fazendo a conversão para a classe base. Isso é útil em alguns cenários, mas perigoso.


2
Por que lançar um método que oculta o método base é perigoso? Ou você está sugerindo que lançar um elenco em geral é perigoso?
Mark

3
@ Mark - um chamador pode não estar ciente da implementação, causando mau uso acidental.
9788 Jon B #

Você pode usar overridee / ou newnão virtualno método pai?
Aaron Franke

16

No primeiro caso, você está ocultando a definição na classe pai. Isso significa que ele será invocado apenas quando você estiver lidando com o objeto como a classe filho. Se você converter a classe para seu tipo pai, o método do pai será chamado. Na segunda instância, o método é substituído e será chamado, independentemente de o objeto ser convertido como filho ou classe pai.


7

tente o seguinte: (case1)

((BaseClass)(new InheritedClass())).DoIt()

Edit: virtual + override são resolvidos em tempo de execução (então a substituição realmente substitui os métodos virtuais), enquanto os novos apenas criam um novo método com o mesmo nome e ocultam o antigo, ele é resolvido em tempo de compilação -> seu compilador chamará o método de ' vê '


3

No caso 1, se você usou o método DoIt () da classe herdada enquanto o tipo é declarado como a classe base, você verá a ação da classe base mesmo.

/* Results
Class1
Base1
Class2
Class2
*/
public abstract class Base1
{
    public void DoIt() { Console.WriteLine("Base1"); }
}
public  class Class1 : Base1 
{
    public new void DoIt() { Console.WriteLine("Class1"); }
}
public abstract class Base2
{
    public virtual void DoIt() { Console.WriteLine("Base2"); }
}
public class Class2 : Base2
{
    public override void DoIt() { Console.WriteLine("Class2"); }
}
static void Main(string[] args)
{
    var c1 = new Class1();
    c1.DoIt();
    ((Base1)c1).DoIt();

    var c2 = new Class2();
    c2.DoIt();
    ((Base2)c2).DoIt();
    Console.Read();
}

Você pode postar o aviso ou erro que está recebendo. Esse código funcionou bem quando eu o postei originalmente.
Matthew Whited

Tudo isso deve ser colado na sua classe de ponto de entrada (Programa). Isso foi removido para permitir uma melhor formatação neste site.
Matthew Whited

3

A diferença entre os dois casos é que, no caso 1, o DoItmétodo base não é substituído, apenas oculto. O que isso significa é que, dependendo do tipo da variável, depende de qual método será chamado. Por exemplo:

BaseClass instance1 = new SubClass();
instance1.DoIt(); // Calls base class DoIt method

SubClass instance2 = new SubClass();
instance2.DoIt(); // Calls sub class DoIt method

Isso pode ser realmente confuso e resultar em comportamento não esperado e deve ser evitado, se possível. Portanto, a maneira preferida seria o caso 2.


3
  • newsignifica respeitar o seu tipo de referência (lado esquerdo de =), executando assim o método dos tipos de referência. Se o método redefinido não tiver uma newpalavra-chave, ele se comportará da mesma maneira que tem. Além disso, também conhecido como herança não polimórfica . Ou seja, "estou criando um método totalmente novo na classe derivada que não tem absolutamente nada a ver com métodos com o mesmo nome na classe base". - por dito Whitaker
  • override, que deve ser usado com a virtualpalavra - chave em sua classe base, significa respeitar o seu tipo OBJECT (lado direito de =), executando o método substituído independentemente do tipo de referência. Além disso, também conhecido como herança polimórfica .

Minha maneira de ter em mente as duas palavras-chave é que elas são opostas uma à outra.

override: virtualpalavra - chave deve ser definida para substituir o método. O método que utiliza a overridepalavra-chave que, independentemente do tipo de referência (referência da classe base ou classe derivada), se for instanciado com a classe base, o método da classe base será executado. Caso contrário, o método da classe derivada será executado.

new: se a palavra-chave for usada por um método, ao contrário da overridepalavra-chave, o tipo de referência é importante. Se for instanciado com a classe derivada e o tipo de referência for a classe base, o método da classe base será executado. Se for instanciado com a classe derivada e o tipo de referência for a classe derivada, o método da classe derivada será executado. Ou seja, é contraste de overridepalavra-chave. Por outro lado, se você esquecer ou omitir adicionar nova palavra-chave ao método, o compilador se comporta por padrão à medida que a newpalavra-chave é usada.

class A 
{
    public string Foo() 
    {
        return "A";
    }

    public virtual string Test()
    {
        return "base test";
    }
}

class B: A
{
    public new string Foo() 
    {
        return "B";
    }
}

class C: B 
{
    public string Foo() 
    {
        return "C";
    }

    public override string Test() {
        return "derived test";
    }
}

Ligue para o principal:

A AClass = new B();
Console.WriteLine(AClass.Foo());
B BClass = new B();
Console.WriteLine(BClass.Foo());
B BClassWithC = new C();
Console.WriteLine(BClassWithC.Foo());

Console.WriteLine(AClass.Test());
Console.WriteLine(BClassWithC.Test());

Resultado:

A
B
B
base test
derived test

Novo exemplo de código,

Brinque com o código comentando um por um.

class X
{
    protected internal /*virtual*/ void Method()
    {
        WriteLine("X");
    }
}
class Y : X
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Y");
    }
}
class Z : Y
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Z");
    }
}

class Programxyz
{
    private static void Main(string[] args)
    {
        X v = new Z();
        //Y v = new Z();
        //Z v = new Z();
        v.Method();
}

1

Se a palavra override- chave for usada na classe derivada, ela substituirá o método pai.

Se a palavra new- chave for usada na classe derive, derive o método oculto pelo método pai.


1

Eu tinha a mesma pergunta e é realmente confusa, você deve considerar que a substituição e as novas palavras-chave funcionem apenas com objetos do tipo classe base e valor da classe derivada. Nesse caso, apenas você verá o efeito de substituir e novo: portanto, se você possui class Ae B, Bherda de A, instancia um objeto como este:

A a = new B();

Agora, ao chamar métodos, o estado será levado em consideração. Substituir : significa que ele estende a função do método e, em seguida, usa o método na classe derivada, enquanto novos dizem ao compilador para ocultar o método na classe derivada e, em vez disso, usar o método na classe base. Aqui está uma visão muito boa desse assunto:

https://msdn.microsoft.com/EN-US/library/ms173153%28v=VS.140,d=hv.2%29.aspx?f=255&MSPPError=-2147217396


1

O artigo abaixo está no vb.net, mas acho que a explicação sobre novas substituições vs é muito fácil de entender.

https://www.codeproject.com/articles/17477/the-dark-shadow-of-overrides

Em algum momento do artigo, existe esta frase:

Em geral, Shadows assume que a função associada ao tipo é chamada, enquanto Overrides assume que a implementação do objeto é executada.

A resposta aceita para esta pergunta é perfeita, mas acho que este artigo fornece bons exemplos para adicionar um significado melhor às diferenças entre essas duas palavras-chave.


1

Fora de tudo isso, o novo é o mais confuso. Através da experimentação, a nova palavra-chave é como dar aos desenvolvedores a opção de substituir a implementação da classe herdada pela implementação da classe base, definindo explicitamente o tipo. É como pensar o contrário.

No exemplo abaixo, o resultado retornará "Resultado derivado" até que o tipo seja explicitamente definido como teste BaseClass, somente então "Resultado base" será retornado.

class Program
{
    static void Main(string[] args)
    {
        var test = new DerivedClass();
        var result = test.DoSomething();
    }
}

class BaseClass
{
    public virtual string DoSomething()
    {
        return "Base result";
    }
}

class DerivedClass : BaseClass
{
    public new string DoSomething()
    {
        return "Derived result";
    }
}

3
Adicione seu comentário se você se opuser. Bater e correr é tão covarde.
usar o seguinte código

0

A diferença funcional não será mostrada nestes testes:

BaseClass bc = new BaseClass();

bc.DoIt();

DerivedClass dc = new DerivedClass();

dc.ShowIt();

Neste exemplo, o Doit que é chamado é o que você espera que seja chamado.

Para ver a diferença, você precisa fazer o seguinte:

BaseClass obj = new DerivedClass();

obj.DoIt();

Você verá se executar esse teste que, no caso 1 (como você o definiu), o DoIt()in BaseClassé chamado, no caso 2 (como você o definiu), o DoIt()in DerivedClassé chamado.


-1

No primeiro caso, ele chamará o método DoIt () da classe derivada porque a nova palavra-chave oculta o método DoIt () da classe base.

No segundo caso, ele chamará DoIt () substituído

  public class A
{
    public virtual void DoIt()
    {
        Console.WriteLine("A::DoIt()");
    }
}

public class B : A
{
    new public void DoIt()
    {
        Console.WriteLine("B::DoIt()");
    }
}

public class C : A
{
    public override void DoIt()
    {
        Console.WriteLine("C::DoIt()");
    }
}

vamos criar instância dessas classes

   A instanceA = new A();

    B instanceB = new B();
    C instanceC = new C();

    instanceA.DoIt(); //A::DoIt()
    instanceB.DoIt(); //B::DoIt()
    instanceC.DoIt(); //B::DoIt()

Tudo é esperado acima. Deixe definir instanceB e instanceC como instanceA e chame o método DoIt () e verifique o resultado.

    instanceA = instanceB;
    instanceA.DoIt(); //A::DoIt() calls DoIt method in class A

    instanceA = instanceC;
    instanceA.DoIt();//C::DoIt() calls DoIt method in class C because it was overriden in class C

instanceC.DoIt (); daria a você C :: DoIt (), não B :: DoIt ()
BYS2 20/05/19

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.