C #: uso de palabras clave virtual + anulación vs. nuevo

¿Cuáles son las diferencias entre declarar un método en un tipo base ” virtual ” y luego override en un tipo secundario utilizando la palabra clave ” override ” en lugar de simplemente usar la palabra clave ” new ” al declarar el método de coincidencia en el tipo secundario?

La palabra clave “nueva” no se anula, significa un nuevo método que no tiene nada que ver con el método de la clase 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 ()); } } 

Esto imprime falso, si lo reemplazó, se imprimiría verdadero.

(Código base tomado de Joseph Daigle)

Entonces, si estás haciendo un polymorphism real, SIEMPRE DEBES ANULAR . El único lugar donde debe usar “nuevo” es cuando el método no está relacionado de ninguna manera con la versión de la clase base.

Siempre encuentro que cosas como esta se entienden más fácilmente con imágenes:

Nuevamente, tomando el 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; } } 

Si luego llamas al código así:

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

NOTA: Lo importante es que nuestro objeto es en realidad una Bar , pero lo estamos almacenando en una variable de tipo Foo (esto es similar a fundirlo)

Luego, el resultado será el siguiente, dependiendo de si usó virtual / override o new al declarar sus clases.

Explicación Virtual / Anular

Aquí hay un código para entender la diferencia en el comportamiento de los métodos virtuales y no virtuales:

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

La new palabra clave realmente crea un miembro completamente nuevo que solo existe en ese tipo específico.

Por ejemplo

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

El método existe en ambos tipos. Cuando usa el reflection y obtiene los miembros del tipo Bar , encontrará dos métodos llamados DoSomething() que se ven exactamente iguales. Al usar new , efectivamente oculta la implementación en la clase base, de modo que cuando las clases se derivan de Bar (en mi ejemplo), la llamada al método base.DoSomething() va a Bar y no a Foo .

virtual / override le dice al comstackdor que los dos métodos están relacionados y que, en algunas circunstancias, cuando piense que está llamando al primer método (virtual), en realidad es correcto llamar al segundo método (anulado). Esta es la base del polymorphism.

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

Llamará al método VirtualFoo () anulado de la Subclase.

new le dice al comstackdor que está agregando un método a una clase derivada con el mismo nombre que un método en la clase base, pero que no tienen relación entre sí.

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

Llamará al método NewBar () de BaseClass, mientras que:

 (new SubClass()).NewBar() 

Llamará al método NewBar () de la Subclase.

Más allá de los detalles técnicos, creo que usar virtual / override comunica mucha información semántica sobre el diseño. Cuando declara virtual un método, indica que espera que las clases implementadoras quieran proporcionar sus propias implementaciones no predeterminadas. Omitir esto en una clase base, del mismo modo, declara la expectativa de que el método predeterminado debería ser suficiente para todas las clases de implementación. Del mismo modo, uno puede usar declaraciones abstractas para obligar a las clases implementadoras a proporcionar su propia implementación. De nuevo, creo que esto comunica mucho acerca de cómo el progtwigdor espera que se use el código. Si estuviera escribiendo tanto la base como las clases de implementación y me encontrara utilizando nuevas, seriamente volvería a pensar en la decisión de no hacer el método virtual en el padre y declarar mi intención específicamente.

La diferencia entre la palabra clave override y la nueva palabra clave es que la primera anula el método y luego oculta el método.

Echa un vistazo a los enlaces siguientes para obtener más información …

MSDN y otros

  • new palabra clave es para ocultar. – significa que está ocultando su método en tiempo de ejecución. La salida se basará en el método de clase base.
  • override para anular – significa que está invocando su método de clase derivado con la referencia de la clase base. La salida se basará en el método de clase derivado.

Mi versión de la explicación proviene del uso de propiedades para ayudar a entender las diferencias.

override es lo suficientemente simple, ¿verdad? El tipo subyacente anula el de los padres.

new es tal vez el engañoso (para mí lo fue). Con propiedades, es más 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 un depurador puedes notar que Foo foo tiene 2 propiedades de GetSomething , ya que en realidad tiene 2 versiones de la propiedad, Foo ‘s y Bar ‘ s, y para saber cuál usar, c # “elige” la propiedad para el tipo actual .

Si quisieras usar la versión del Bar, hubieras utilizado la anulación o el Foo foo lugar.

Bar bar tiene solo 1 , ya que quiere un comportamiento completamente nuevo para GetSomething .