¿Por qué necesitamos la nueva palabra clave y por qué es el comportamiento predeterminado para ocultar y no anular?

Estaba viendo esta publicación en el blog y tenía las siguientes preguntas:

  • ¿Por qué necesitamos la palabra clave new ¿Es solo para especificar que se está ocultando un método de clase base? Quiero decir, ¿por qué lo necesitamos? Si no usamos la palabra clave override , ¿no estamos ocultando el método de la clase base?
  • ¿Por qué se oculta el valor predeterminado en C # y no se anula? ¿Por qué los diseñadores lo han implementado de esta manera?

Buena pregunta. Permítanme volver a establecerlos.

¿Por qué es legal ocultar un método con otro método?

Déjame responder esa pregunta con un ejemplo. Usted tiene una interfaz de CLR v1:

 interface IEnumerable { IEnumerator GetEnumerator(); } 

Súper. Ahora en CLR v2 tienes generics y piensas “hombre, si hubiéramos tenido generics en v1, habría hecho de esto una interfaz genérica. Pero no lo hice. Debo hacer algo compatible con eso ahora que es genérico para que Obtengo los beneficios de los generics sin perder compatibilidad con versiones anteriores con código que espera IEnumerable “.

 interface IEnumerable : IEnumerable { IEnumerator .... uh oh 

¿Cómo vas a llamar al método GetEnumerator de IEnumerable ? Recuerde que desea ocultar GetEnumerator en la interfaz base no genérica. Nunca querrás que se invoque esa cosa a menos que estés explícitamente en una situación de compatibilidad hacia atrás.

Solo eso justifica la ocultación del método. Para obtener más ideas sobre las justificaciones de la ocultación de métodos, consulte mi artículo sobre el tema .

¿Por qué esconderse sin “nuevo” causa una advertencia?

Porque queremos llamar su atención sobre que está ocultando algo y que podría hacerlo accidentalmente. Recuerde, es posible que esté ocultando algo accidentalmente debido a una edición de la clase base realizada por otra persona, en lugar de editar su clase derivada.

¿Por qué se esconde sin “nuevo” una advertencia en lugar de un error?

Misma razón. Es posible que esté ocultando algo accidentalmente porque acaba de adquirir una nueva versión de una clase base. Esto sucede todo el tiempo. FooCorp crea una clase base B. BarCorp crea una clase derivada D con un método Bar, porque a sus clientes les gusta ese método. FooCorp lo ve y dice “hey”, esa es una buena idea, podemos poner esa funcionalidad en la clase base. Lo hacen y envían una nueva versión de Foo.DLL, y cuando BarCorp escoja la nueva versión, sería bueno que les dijeran que su método ahora oculta el método de la clase base.

Queremos que esa situación sea una advertencia y no un error, porque convertirlo en un error significa que esta es otra forma del problema de la clase base frágil . C # ha sido cuidadosamente diseñado para que cuando alguien realice un cambio en una clase base, los efectos en el código que utiliza una clase derivada se minimicen.

¿Por qué se esconde y no anula el valor predeterminado?

Porque la anulación virtual es peligrosa . La anulación virtual permite que las clases derivadas cambien el comportamiento del código comstackdo para usar clases base. Hacer algo peligroso como hacer una anulación debe ser algo que haga consciente y deliberadamente , no por accidente.

Si el método en la clase derivada está precedido por la palabra clave nueva, el método se define como independiente del método en la clase base

Sin embargo, si no especifica ni nuevas ni anulaciones, el resultado resultante es el mismo que si especificara nuevo, pero obtendrá una advertencia del comstackdor (ya que puede no ser consciente de que está ocultando un método en el método de la clase base, o de hecho, es posible que haya querido anularlo, y simplemente se olvidó de incluir la palabra clave).

Por lo tanto, le ayuda a evitar errores y a mostrar explícitamente lo que desea hacer, y hace que el código sea más legible, por lo que uno puede entender fácilmente su código.

Vale la pena señalar que el único efecto de lo new en este contexto es suprimir una Advertencia. No hay cambio en la semántica.

Entonces, una respuesta es: necesitamos algo new para indicarle al comstackdor que el ocultamiento es intencional y eliminar la advertencia.

La pregunta de seguimiento es: si no puede / no puede anular un método, ¿por qué debería introducir otro método con el mismo nombre? Porque esconderse es, en esencia, un conflicto de nombres. Y, por supuesto, lo evitarías en la mayoría de los casos.

La única buena razón por la que se me ocurre ocultar intencionalmente es cuando una interfaz te obliga a utilizar un nombre.

En C # los miembros están sellados por defecto, lo que significa que no puede anularlos (a menos que estén marcados con las palabras clave virtual o abstract ) y esto por motivos de rendimiento . El nuevo modificador se usa para ocultar explícitamente un miembro heredado.

Si la anulación fue predeterminada sin especificar la palabra clave de override , podría anular accidentalmente algún método de su base simplemente debido a la igualdad del nombre.

La estrategia del comstackdor de .Net es emitir advertencias si algo puede salir mal, solo para estar seguro, por lo que en este caso si la anulación fue por defecto, debería haber una advertencia para cada método reemplazado – algo como ‘advertencia: verificar si realmente quieres para anular’.

Mi conjetura se debe principalmente a la herencia de interfaz múltiple. Usando interfaces discretas, sería muy posible que dos interfaces distintas usen la misma firma de método. Permitir el uso de la new palabra clave le permitirá crear estas diferentes implementaciones con una clase, en lugar de tener que crear dos clases distintas.

ActualizadoEric me dio una idea sobre cómo mejorar este ejemplo.

 public interface IAction1 { int DoWork(); } public interface IAction2 { string DoWork(); } public class MyBase : IAction1 { public int DoWork() { return 0; } } public class MyClass : MyBase, IAction2 { public new string DoWork() { return "Hi"; } } class Program { static void Main(string[] args) { var myClass = new MyClass(); var ret0 = myClass.DoWork(); //Hi var ret1 = ((IAction1)myClass).DoWork(); //0 var ret2 = ((IAction2)myClass).DoWork(); //Hi var ret3 = ((MyBase)myClass).DoWork(); //0 var ret4 = ((MyClass)myClass).DoWork(); //Hi } } 

Como se señaló, el ocultamiento de método / propiedad permite cambiar las cosas sobre un método o propiedad que de otro modo no podría cambiarse fácilmente. Una situación donde esto puede ser útil es permitir que una clase heredada tenga propiedades de lectura y escritura que sean de solo lectura en la clase base. Por ejemplo, supongamos que una clase base tiene un conjunto de propiedades de solo lectura denominadas Value1-Value40 (por supuesto, una clase real usaría mejores nombres). Un descendiente sellado de esta clase tiene un constructor que toma un objeto de la clase base y copia los valores desde allí; la clase no les permite ser cambiado después de eso. Un descendiente heredable diferente declara una propiedad de lectura-escritura llamada Value1-Value40 que, cuando se lee, se comporta igual que las versiones de la clase base pero, cuando se escribe, permite que se escriban los valores. El efecto neto será que el código que quiere una instancia de la clase base que sabe que nunca cambiará puede crear un nuevo objeto de la clase de solo lectura, que puede copiar datos desde un objeto pasado sin tener que preocuparse de si ese objeto es de solo lectura o de lectura y escritura.

Una molestia con este enfoque, tal vez alguien me puede ayudar, es que no conozco una manera de sombrear y anular una propiedad en particular dentro de la misma clase. ¿Alguno de los lenguajes CLR permite eso (yo uso vb 2005)? Sería útil si el objeto de la clase base y sus propiedades pudieran ser abstractos, pero eso requeriría una clase intermedia para anular las propiedades de Valor1 a Valor40 antes de que una clase descendiente pudiera darles sombra.

Tiene razón en que la nueva palabra clave oculta explícitamente a un miembro de la clase base para que las personas que llaman terminen usando el miembro en su subclase en lugar de la versión de la superclase. Como esa publicación de blog también menciona, también puedes hacer esto implícitamente.

Eso es diferente a anular a un miembro de la clase base; para hacer eso, el miembro de la clase base debe marcarse como abstracto o virtual. Entonces, no todos los miembros de una clase base pueden ser anulados. Supongamos que está subclasificando una clase del ensamblado de otra persona y no tiene acceso, o no quiere, retroceder y actualizar ese código para hacer que los miembros sean virtuales. Sin la nueva palabra clave estarías atascado.

Puedes discutir si es mejor tener la palabra clave y ser explícito (mi opinión) o simplemente hacerlo de manera implícita.