¿Por qué C # implementa los métodos como no virtuales de forma predeterminada?

A diferencia de Java, ¿por qué C # trata los métodos como funciones no virtuales por defecto? ¿Es más probable que sea un problema de rendimiento en lugar de otros posibles resultados?

Me acuerdo de leer un párrafo de Anders Hejlsberg sobre varias ventajas que está sacando la architecture existente. Pero, ¿qué pasa con los efectos secundarios? ¿Es realmente una buena solución tener métodos no virtuales por defecto?

Las clases deben diseñarse para la herencia para poder aprovecharla. Tener métodos virtual por defecto significa que todas las funciones de la clase pueden ser conectadas y reemplazadas por otra, lo que no es realmente algo bueno. Muchas personas incluso creen que las clases deberían haber sido sealed por defecto.

virtual métodos virtual también pueden tener una ligera implicación en el rendimiento. Sin embargo, esta no es la razón principal.

Me sorprende que parezca haber un consenso aquí que no virtual-por-defecto es la forma correcta de hacer las cosas. Voy a caer en el otro lado, creo que pragmático, de la valla.

La mayoría de las justificaciones me parecen el viejo argumento “Si te damos el poder, puedes lastimarte”. De los progtwigdores ?!

Me parece que el codificador que no sabe lo suficiente (o tiene suficiente tiempo) para diseñar su biblioteca para la herencia y / o extensibilidad es el codificador que ha producido exactamente la biblioteca que probablemente tenga que arreglar o retocar, exactamente el biblioteca donde la capacidad de sobrescribir sería más útil.

La cantidad de veces que he tenido que escribir un código de trabajo desesperado y desesperado (o abandonar el uso y rodar mi propia solución alternativa) porque no puedo sobrescribir, supera con creces la cantidad de veces que me mordieron ( por ejemplo, en Java) anulando donde el diseñador podría no haber considerado posible.

Non-virtual-by-default hace mi vida más difícil.

ACTUALIZACIÓN: Se ha señalado [bastante correctamente] que en realidad no respondí la pregunta. Entonces, y con mis disculpas por llegar un poco tarde …

Quería ser capaz de escribir algo conciso como “C # implementa los métodos como no virtuales de forma predeterminada porque se tomó una mala decisión que valoraba más los progtwigs que los progtwigdores”. (Creo que podría justificarse en cierta medida en función de algunas de las otras respuestas a esta pregunta, como el rendimiento (optimización prematura, ¿alguien?), O garantizar el comportamiento de las clases).

Sin embargo, me doy cuenta de que simplemente estaría expresando mi opinión y no la respuesta definitiva que Stack Overflow desea. Seguramente, pensé, al más alto nivel la respuesta definitiva (pero inútil) es:

No son virtuales por defecto porque los diseñadores del lenguaje tuvieron que tomar una decisión y eso es lo que eligieron.

Ahora supongo que la razón exacta por la que tomaron esa decisión nunca lo haremos … ¡oh, espera! ¡La transcripción de una conversación!

Parece que las respuestas y comentarios aquí sobre los peligros de las API predominantes y la necesidad de diseñar explícitamente para la herencia van por el buen camino, pero a todos les falta un aspecto temporal importante: la principal preocupación de Anders era mantener implícitas las API o las clases. contrato en todas las versiones . Y creo que en realidad está más preocupado por permitir que la plataforma .Net / C # cambie bajo el código en lugar de preocuparse por el cambio de código de usuario en la parte superior de la plataforma. (Y su punto de vista “pragmático” es exactamente el opuesto al mío porque está mirando desde el otro lado).

(¿Pero no podrían simplemente haber elegido virtual por defecto y luego salpicado de “final” a través de la base de código? Tal vez eso no es lo mismo … y Anders es claramente más inteligente que yo, así que voy a dejar que mienta).

Porque es muy fácil olvidar que un método puede ser anulado y no diseñado para eso. C # te hace pensar antes de hacerlo virtual. Creo que esta es una gran decisión de diseño. Algunas personas (como Jon Skeet) incluso han dicho que las clases deben estar selladas por defecto.

Para resumir lo que otros dijeron, hay algunas razones:

1- En C #, hay muchas cosas en syntax y semántica que vienen directamente de C ++. El hecho de que los métodos no sean virtuales por defecto en C ++ influyó en C #.

2- Tener todos los métodos virtuales por defecto es un problema de rendimiento porque cada llamada al método debe usar la tabla virtual del objeto. Además, esto limita fuertemente la capacidad del comstackdor Just-In-Time para alinear métodos y realizar otros tipos de optimización.

3- Lo más importante, si los métodos no son virtuales por defecto, puedes garantizar el comportamiento de tus clases. Cuando son virtuales por defecto, como en Java, ni siquiera se puede garantizar que un método getter simple funcione como se pretende porque se podría anular para hacer algo en una clase derivada (por supuesto, puede, y debe, hacer el método y / o la clase final).

Uno podría preguntarse, como lo mencionó Zifre, por qué el lenguaje C # no va un paso más allá y hace que las clases estén selladas por defecto. Eso es parte de todo el debate sobre los problemas de la herencia de la implementación, que es un tema muy interesante.

C # está influenciado por C ++ (y más). C ++ no habilita el despacho dynamic (funciones virtuales) por defecto. Un argumento (¿bueno?) Para esto es la pregunta: “¿Con qué frecuencia implementan clases que son miembros de una jerarquía de clases?”. Otra razón para evitar habilitar el despacho dynamic por defecto es la huella de memoria. Una clase sin un puntero virtual (vpointer) apuntando a una tabla virtual , por supuesto es más pequeña que la clase correspondiente con el enlace activo habilitado.

El problema de rendimiento no es tan fácil de decir “sí” o “no” a. La razón de esto es la comstackción Just In Time (JIT), que es una optimización de tiempo de ejecución en C #.

Otra pregunta similar sobre ” velocidad de llamadas virtuales … ”

La simple razón es el costo de diseño y mantenimiento además de los costos de rendimiento. Un método virtual tiene un costo adicional en comparación con un método no virtual porque el diseñador de la clase debe planificar lo que sucede cuando el método es anulado por otra clase. Esto tiene un gran impacto si esperas que un método en particular actualice el estado interno o tenga un comportamiento particular. Ahora tiene que planear qué sucede cuando una clase derivada cambia ese comportamiento. Es mucho más difícil escribir código confiable en esa situación.

Con un método no virtual, tienes control total. Todo lo que sale mal es culpa del autor original. El código es mucho más fácil de razonar.

Si todos los métodos de C # fueran virtuales, la vtbl sería mucho más grande.

Los objetos C # solo tienen métodos virtuales si la clase tiene métodos virtuales definidos. Es cierto que todos los objetos tienen información de tipo que incluye un equivalente vtbl, pero si no se definen métodos virtuales, entonces solo estarán presentes los métodos básicos del Objeto.

@Tom Hawtin: Probablemente sea más exacto decir que C ++, C # y Java son todos de la familia C de idiomas 🙂

Ciertamente no es un problema de rendimiento. El intérprete Java de Sun usa el mismo código para despachar ( invokevirtual bytecode) y HotSpot genera exactamente el mismo código, ya sea final o no. Creo que todos los objetos C # (pero no las estructuras) tienen métodos virtuales, por lo que siempre necesitará la identificación de clase vtbl / runtime. C # es un dialecto de “lenguajes similares a Java”. Sugerir que proviene de C ++ no es del todo honesto.

Existe la idea de que debe “diseñar para herencia o prohibirlo”. Lo cual suena como una gran idea hasta el momento en que tienes un caso comercial grave para poner una solución rápida. Quizás heredando un código que no controlas.

Viniendo de un fondo Perl, creo que C # selló la perdición de cada desarrollador que podría haber querido extender y modificar el comportamiento de una clase base “a través de un método no virtual sin forzar a todos los usuarios de la nueva clase a estar al tanto del potencial detrás de la escena detalles.

Considere el método Add de la clase List. ¿Qué sucede si un desarrollador desea actualizar una de varias bases de datos potenciales cada vez que se agrega una Lista en particular? Si ‘Agregar’ hubiera sido virtual de manera predeterminada, el desarrollador podría desarrollar una clase ‘BackedList’ que anulara el método ‘Agregar’ sin forzar a todo el código del cliente a saber que era una ‘Lista Respaldada’ en lugar de una ‘Lista’ regular. Para todos los propósitos prácticos, ‘BackedList’ puede verse como una ‘Lista’ más del código del cliente.

Esto tiene sentido desde la perspectiva de una gran clase principal que podría proporcionar acceso a uno o más componentes de la lista que están respaldados por uno o más esquemas en una base de datos. Dado que los métodos C # no son virtuales por defecto, la lista proporcionada por la clase principal no puede ser una simple instancia de IEnumerable o ICollection o incluso una lista, sino que debe anunciarse al cliente como ‘BackedList’ para garantizar que la nueva versión de la operación ‘Agregar’ se llama para actualizar el esquema correcto.