¿Por qué 'void' no está permitido como un tipo genérico en C #?

53

¿Cuáles fueron las decisiones de diseño que argumentaron a favor de voidno ser construible y no ser permitido como un tipo genérico? Después de todo, es solo un vacío especial structy habría evitado la PITA total de tener distintos Funcy Actiondelegados.

(C ++ permite voiddevoluciones explícitas y permite voidcomo parámetro de plantilla)

Thomas Owens
fuente
13
@Oded Teniendo en cuenta que Eric Lippert tiene una cuenta y participa en programadores , creo que acaba de hacerlo.
Thomas Owens
2
@BenVoigt No necesariamente requiere el conocimiento de un solo individuo, aunque hay una sola persona aquí que puede (fácilmente, supongo) dar la respuesta correcta. Cualquier persona familiarizada con la especificación probablemente pueda responder la pregunta, especialmente si tiene un conocimiento del diseño del lenguaje de programación. También es una pregunta interesante de diseño de lenguaje / implementación de lenguaje, que es sobre el tema aquí.
Thomas Owens
66
@BenVoigt: ¿Por qué estás tan interesado en tener una pregunta perfectamente válida e interesante cerrada sin ninguna buena razón, otra para satisfacer tu propia pedantería? Tuve una búsqueda y no pude encontrar la respuesta; tal vez alguien aquí haya leído una publicación de blog al respecto, tal vez las personas que tomaron la decisión quieran responder a la pregunta, tal vez alguien haya conversado con las personas que tomaron la decisión al respecto y les hicieron la pregunta ellos mismos y puedan transmitir la respuesta. He dejado de publicar en este sitio y SO porque la gente parece mucho más interesada en cerrar una pregunta que en responder preguntas.
44
Podría señalar que las preguntas y respuestas del "gran punto" aquí y especialmente en SO son generalmente de la forma "¿qué significa esta sintaxis?" Esas preguntas casi nunca se cierran porque todos pueden apilar y desplegar las definiciones de punteros constantes para convertir objetos. Las preguntas más interesantes que se encuentran fuera del circuito turístico son expulsadas del sitio por alguna razón pedante: en serio, solo son unos pocos kilobytes en un almacén de datos en alguna parte. ¿Podemos todos relajarnos y aceptar el aprendizaje en lugar de imponer reglas arbitrariamente interpretadas que tienen muy poco propósito?
2
@ThomasOwens: Estoy en un sentido más activo en este sitio que en SO. En este sitio publico una respuesta a aproximadamente una pregunta de cada 300. En SO, publico una respuesta a aproximadamente una pregunta de cada 1500. La diferencia en la actividad de gran número de respuestas es atribuible al número mucho mayor de oportunidades para respondiendo preguntas de C # sobre SO.
Eric Lippert

Respuestas:

69

El problema fundamental con "vacío" es que no significa lo mismo que cualquier otro tipo de retorno. "vacío" significa "si este método regresa, entonces no devuelve ningún valor". No nulo; nulo es un valor. No devuelve ningún valor en absoluto.

Esto realmente arruina el sistema de tipos. Un sistema de tipos es esencialmente un sistema para hacer deducciones lógicas sobre qué operaciones son válidas en valores particulares; un método de devolución nulo no devuelve un valor, por lo que la pregunta "¿qué operaciones son válidas en esta cosa?" no tiene ningún sentido en absoluto. No hay "cosa" para que haya una operación, válida o inválida.

Además, esto arruina el tiempo de ejecución algo feroz. El tiempo de ejecución .NET es una implementación del Sistema de ejecución virtual, que se especifica como una máquina de pila. Es decir, una máquina virtual donde todas las operaciones se caracterizan en términos de su efecto en una pila de evaluación. (Por supuesto, en la práctica, la máquina se implementará en una máquina con pila y registros, pero el sistema de ejecución virtual solo supone una pila). El efecto de una llamada a un método vacío es fundamentalmentediferente al efecto de una llamada a un método no nulo; Un método no nulo siempre pone algo en la pila, que puede ser necesario quitar. Un método nulo nunca pone algo en la pila. Y, por lo tanto, el compilador no puede tratar los métodos nulos y no nulos de la misma manera en el caso en que se ignora el valor devuelto del método; Si el método es nulo, no hay valor de retorno, por lo que no debe haber pop.

Por todas estas razones, "vacío" no es un tipo que pueda ser instanciado; no tiene valores , de eso se trata. ¡No es convertible en objeto, y un método de retorno nulo nunca puede tratarse polimórficamente con un método de retorno nulo porque hacerlo corrompe la pila!

Por lo tanto, void no se puede usar como argumento de tipo, lo cual es una pena, como se observa. Sería muy conveniente

Con el beneficio de la retrospectiva, hubiera sido mejor para todos los interesados ​​si, en lugar de nada, un método de retorno de vacío devolviera automáticamente "Unidad", un tipo de referencia de un solo objeto mágico. Entonces sabría que cada llamada a un método pone algo en la pila , sabrá que cada llamada a un método devuelve algo que podría asignarse a una variable de tipo de objeto y, por supuesto, la Unidad podría usarse como un argumento de tipo , por lo que habría no es necesario tener tipos de delegado Action y Func separados. Lamentablemente, ese no es el mundo en el que estamos.

Para algunos pensamientos más en este sentido, ver:

Eric Lippert
fuente
C ++ lo admite sin tener que usar un tipo de singleton mágico. Pero supongo que tiene que ver con C ++ usando un estilo "genérico" completamente diferente al de C #.
Winston Ewert
44
@ WinstonEwert: estoy bastante seguro de que C ++ no admite genéricos en absoluto, sino que tiene plantillas, que son fundamentalmente diferentes. Según tengo entendido, con las plantillas, el compilador está haciendo una búsqueda global y reemplaza con sus parámetros de tipo, pero con los genéricos, el sistema de tipos está creando un tipo adecuado. También creo que es por eso que los errores de plantilla de C ++ fueron tan obtusos. Estoy seguro de que Eric me corregirá si digo algo que no está bien
Adam Rackis
1
Creo que todas las estructuras se manejan en tiempo de compilación para obtener una ubicación adecuada en la pila y en otros lugares donde están en línea. Por Voidlo tanto, debería estar bien que se pase como una cantidad infinita de argumentos dentro de 0 bytes de la pila. Cuando cualquier estructura necesita convertirse objecto llamar al método (para obtener this), se coloca en el cuadro con la colocación en el montón con la Typereferencia insertada antes del área de campos de memoria (para muchas implementaciones de CLR) y, por lo tanto, se puede manejar correctamente. En cuanto a mí, el tipo es solo una agrupación de objetos que se pueden distinguir por algunas características (campos). VoidEs solo un objeto.
ony
1
@ OlivierJacot-Descombes: si voidfuera un tipo de valor vacío, esa declaración se traduciría en cero instrucciones del código de máquina. No es muy útil para el tipo codificado Void, pero es útil en casos donde un tipo tiene múltiples parámetros genéricos pero algunos no son necesarios en ciertos casos.
supercat
2
@EricLippert: el manejo adecuado de los valores de retorno de tipo de valor requeriría la capacidad de extraer un número variable de palabras de la pila. ¿Habría alguna dificultad fundamental para permitir que el número de palabras sea cero? Si la persona que llama es responsable de asignar espacio y pasar un byref, el sistema de tipo tendría que reconocer que un byref vacío no tiene significado, y tal vez no se consideró que valiera la pena el esfuerzo, pero no veo ningún problema "fundamental" .
supercat
9

De: http://connect.microsoft.com/VisualStudio/feedback/details/94226/allow-void-to-be-parameter-of-generic-class-if-not-in-c-then-just-in- tiempo de ejecución

Esto es en realidad por diseño: no permitimos ninguna instancia del tipo vacío (junto con muchos otros tipos en el tiempo de ejecución). Tampoco puede crear un vacío vacío o vacío []. Conectamos estos agujeros tipo por seguridad.

Por mi vida no puedo ver cuán vacío es un agujero de seguridad, pero ... eso dice.

Winston Ewert
fuente