¿Por qué son ilegales los métodos virtuales privados en C#?


Viniendo de un fondo de C++, esto me sorprendió. En C++ es una buena práctica hacer que las funciones virtuales sean privadas. De http://www.gotw.ca/publications/mill18.htm : "Directriz #2: Prefiere hacer privadas las funciones virtuales."

También cito el blog de Eric Lippert, de Knights-knaves-protected-and-internal :

Los métodos virtuales privados son ilegales en C#, lo que me molesta mucho. Me usaría totalmente esa característica si tuviéramos se.

Entiendo que en C#, no sería capaz de anular un método virtual privado en una clase derivada (pero no anidada). ¿Por qué es este el caso? En C++ el especificador de acceso no tiene nada que ver con si se puede anular una función o no.

Author: user, 2010-06-21

6 answers

Observo que hay dos preguntas aquí. En el futuro, podría considerar publicar dos preguntas en lugar de combinar dos preguntas en una. Cuando combinas preguntas como esta a menudo lo que sucede es que solo la primera recibe respuesta.

La primera pregunta es "¿por qué los métodos virtuales privados son ilegales en C#?"

Aquí están los argumentos contra la característica "métodos virtuales privados":

  1. Private virtual solo es útil cuando tiene una clase derivada anidada. Este es un patrón útil, pero mucho menos común que la situación de clase derivada no anidada.

  2. Si desea restringir la capacidad de sobreescribir el método en clases derivadas no anidadas, puede hacerlo restringiendo la capacidad de las clases no anidadas para derivar de la clase base; haga que todos los constructores de clases base sean privados. Por lo tanto, private virtual no es necesario para evitar la sobreescritura; protected virtual es suficiente, porque las únicas clases derivadas serán anidar.

  3. Si desea restringir la capacidad de llamar a un método en una clase derivada no anidada, puede hacer que el método sea virtual interno y luego decirle a sus compañeros de trabajo que no usen ese método. Es irritante que el compilador no haga cumplir esto, pero el compilador tampoco hace cumplir ninguna otra restricción semántica sobre cómo se supone que debe usarse un método; obtener la semántica correcta es su negocio, no el del compilador, y tiene que hacer cumplir eso con revisiones apropiadas del código. Por lo tanto, private virtual no es necesario para evitar llamadas; las revisiones internas de código virtual plus son suficientes.

  4. Es posible implementar este patrón ya con partes existentes:

    abstract class C
    {
        private int CF() { whatever; }
        private Func<int> f;
        public C() { f = CF; } 
        private int F() { return f(); }
        private class D : C
        {
            private int DF() { whatever; }
            public D() { f = DF; }
        }
    

    Ahora tengo un método F que es efectivamente virtual, pero solo puede ser "sobrescrito" por clases anidadas derivadas.

Dado que en todos los casos ya sea protegido, interno o interno protegido hace el truco, privado virtual es innecesario. Casi nunca es lo correcto, ya que tienes que estar ya comprometido a usar el patrón de clase derivado anidado. Entonces, el lenguaje lo hace ilegal.

Los argumentos para son:

Ha habido veces en código real en las que quiero que un método virtual sea un detalle de implementación privada de una clase que quiero que se extienda tanto por clases internas no anidadas como por clases internas anidadas. Tener que imponer el invariante para que el método interno no sea llamado por mis compañeros de trabajo son irritantes; me gustaría que el compilador lo hiciera cumplir sin tener que saltar a través de aros locos como hacer un campo de tipo delegado, etc.

También, está simplemente la cuestión de consistencia y ortogonalidad. Parece raro que dos cosas que deberían ser independientes accessibility accesibilidad y virtualidad have tengan un efecto en la otra innecesariamente.

Los argumentos en contra de la característica son bastante fuertes. Los argumentos a favor son bastante débiles. Por lo tanto, no hay tal función. Personalmente me gustaría mucho,pero entiendo totalmente por qué el equipo de diseño nunca me ha aceptado. No vale la pena el costo, y odiaría no enviar una mejor característica porque gastamos presupuesto en una característica que no beneficia a casi nadie.

La segunda pregunta es "¿Por qué en C# no es capaz de anular un método virtual privado en una clase derivada no anidada?"

Hay varias razones.

  1. Porque solo puedes anular lo que puedo ver. Un método privado es un detalle de implementación privada de una clase base y no debe ser accesible.

  2. Porque permitir eso tiene serias implicaciones de seguridad. Recuerde, en C++ casi siempre compila código en una aplicación a la vez. Tienes el código fuente para todo; todo es esencialmente "interno" desde la perspectiva de C++ la mayor parte del tiempo. En C#, ese no es el caso. Asambleas de terceros pueden obtener fácilmente en los tipos públicos de bibliotecas y producir nuevas extensiones a esas clases que luego se pueden utilizar sin problemas en lugar de instancias de la clase base. Dado que los métodos virtuales efectivamente cambian el comportamiento de una clase, cualquier código que dependa por razones de seguridad de invariantes de esa clase necesita ser cuidadosamente diseñado para que no dependan de invariantes garantizados por la clase base. Restringir la accesibilidad de los métodos virtuales ayuda a garantizar que los invariantes de esos métodos sean mantener.

  3. Porque permitir eso proporciona otra forma del problema de la clase base frágil. C# ha sido cuidadosamente diseñado para ser menos susceptible al problema de la clase base frágil que otros lenguajes OO. Si un método virtual inaccesible se puede sobrescribir en una clase derivada, ese detalle de implementación privada de la clase base se convierte en un cambio de ruptura si se altera. Los proveedores de clases base deben ser libres de cambiar sus detalles internos sin preocuparse demasiado de que han roto las clases derivadas que dependen de ellos; idealmente, solo la interfaz pública documentada de un tipo debe mantenerse cuando cambian los detalles de la implementación.

 35
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-13 13:32:04

Debido a que los métodos privados SOLO se pueden acceder desde la clase que los define, por lo tanto, un método virtual privado sería inútil. Lo que quieres es un método virtual protegido. Un método protegido puede ser accedido por la clase que lo define y cualquier subclase.

EDITAR:

Al proporcionar palabras clave privadas y protegidas, C# le permite un control más granular sobre sus métodos. Eso es privado significa completamente cerrado y protegido significa completamente cerrado aparte de las subclases. Esto permite tienes métodos que solo tu superclase conoce y métodos que las subclases pueden conocer.

 15
Author: Darko Z,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-21 05:38:02

Supongo que la razón es que internal virtualhace casi lo mismo que private virtual y es algo menos confuso para aquellos que no están familiarizados con el idioma private virtual.

Mientras que solo las clases internas pueden anular los métodos private virtual, solo classess en su ensamblado puede anular los métodos internal virtual.

 4
Author: Dean Harding,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-21 05:42:56

En C# (y en la CLI, por lo que he visto), "privado" tiene un significado bastante claro e inequívoco: "accesible solo en esta clase". El concepto de virtualidades privadas lo arruina todo, sin mencionar que los espacios de nombres son un campo de minas. ¿Por qué debería importarme lo que has llamado a un método que ni siquiera puedo ver, y recibir una advertencia del compilador por haber elegido un nombre que ya has enganchado para él?

 2
Author: cHao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-21 05:57:02

Porque C# no tiene ningún mecanismo para proporcionar herencia pública/privada/protegida, que es lo que realmente busca.

Incluso en C++, las clases derivadas no pueden acceder a los miembros privados, pero pueden limitar la visibilidad de la clase base especificando la visibilidad de la herencia:

class Derived : /*->>*/private/*<--*/ Base {
}

C# proporciona un montón de otras cosas para que puedas controlar la visibilidad de los miembros de tu clase. Entre protected y internal, debería ser capaz de obtener la jerarquía exactamente como quieras.

En mi humilde opinión C# impone una relación más fuerte a través de la herencia de una sola clase base, por lo que tiene sentido que si un automóvil tiene un motor, una subclase BMW no debería poder ocultarlo.

C++ admite herencia múltiple, que es una relación IS-A menos estricta, es casi como una relación HAS - A en la que se pueden incorporar múltiples clases no relacionadas. Debido a la capacidad de incorporar varias clases base, desea un control más estricto sobre la visibilidad de todas ellas.

 2
Author: Igor Zevaka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-21 23:20:05

Permítanme dejar esto claro: C# no es C++.

C# se diseña varias décadas después de C++ y se construye utilizando conocimientos avanzados a lo largo de los años. En mi humilde opinión C# está bien definido y finalmente maneja la orientación del objeto de la manera correcta (en mi humilde opinión). Incluye la instrucción internal por una razón y no le permite "virtualizar" y anular métodos privados. Por una razón.

Todas las cuestiones descritas anteriormente (las clases internas anulan los métodos private virtual, utilizando el resumen patrón de fábrica de esta manera, etc...) se puede escribir fácilmente de una manera diferente usando interfaces y la instrucción internal. Dicho esto, debo decir que es bastante una cuestión de gusto si te gusta el modo C++ o el modo C#.

Prefiero usar código descriptivo (código que habla por sí mismo, sin usar comentarios) y uso interfaces en lugar de herencia profunda. Anular métodos virtuales privados se siente como hacking o espaguetis para mí, independientemente de si es una práctica común, un uso frecuente patrón o hace el trabajo.

He estado desarrollando en C++ durante casi 1.5 décadas y nunca me encontré con la necesidad de anular los métodos privados... (Puedo ver los comentarios volando en: -))

 1
Author: Rob Vermeulen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-21 07:32:23