¿Cómo se compara la varianza del sitio de uso de Java con la varianza del sitio de statement de C #?

Entiendo que la especificación de la varianza para los generics en C # ocurre en el nivel de statement de tipo: cuando está creando su tipo genérico, especifica la varianza para los argumentos de tipo. En Java, por otro lado, se especifica la varianza donde se usa un genérico: cuando se crea una variable de algún tipo genérico, se especifica cómo pueden variar sus argumentos de tipo.

¿Cuáles son los pros y los contras de cada opción?

    Solo voy a responder a las diferencias entre el sitio de statement y la varianza del sitio de uso, ya que, aunque los generics de C # y Java difieren de muchas otras maneras, esas diferencias son en su mayoría ortogonales a la varianza.

    Primero, si recuerdo correctamente, la varianza del sitio de uso es estrictamente más poderosa que la varianza del sitio de statement (aunque a costa de la concisión), o al menos los comodines de Java son (que en realidad son más poderosos que la varianza del sitio de uso). Este aumento de potencia es particularmente útil para los lenguajes en los que las construcciones con estado se usan mucho, como C # y Java (pero Scala lo es mucho menos, especialmente porque sus listas estándar son inmutables). Considere la List (o IList ). Como tiene métodos para agregar E y obtener E, es invariable con respecto a E, por lo que la varianza del sitio de statement no se puede usar. Sin embargo, con la varianza del sitio de uso, puede decir List< +Number> para obtener el subconjunto covariante de List y List< -Number> para obtener el subconjunto contravariante de List . En un lenguaje de statement del sitio, el diseñador de la biblioteca tendría que hacer interfaces separadas (o clases si permite herencia múltiple de clases) para cada subconjunto y hacer que List amplíe esas interfaces. Si el diseñador de la biblioteca no hace esto (obsérvese que IEnumerable C # solo hace un pequeño subconjunto de la parte covariante de IList ), entonces no tiene suerte y debe recurrir a las mismas molestias que tiene que hacer en un idioma sin cualquier tipo de varianza

    Así que esa es la ventaja de la herencia del sitio de uso sobre la herencia del sitio de statement. La ventaja de la herencia del sitio de statement sobre la herencia del sitio de uso es básicamente concisa para el usuario (siempre que el diseñador haya realizado el esfuerzo de separar cada clase / interfaz en sus porciones covariantes y contravariantes). Para algo como IEnumerable o IEnumerable , es bueno no tener que especificar la covarianza cada vez que utiliza la interfaz. Java lo hizo especialmente molesto al usar una syntax larga (a excepción de la bivariancia para la cual la solución de Java es básicamente ideal).

    Por supuesto, estas dos características del lenguaje pueden coexistir. Para los parámetros de tipo que son naturalmente covariantes o contravariantes (como en IEnumerable / IEnumerable ), declararlo en la statement. Para los parámetros de tipo que son naturalmente invariables (como en la (I)List ), declare qué tipo de varianza desea cada vez que la use. Simplemente no especifique una varianza del sitio de uso para los argumentos con una varianza del sitio de statement ya que eso hace que las cosas sean confusas.

    Hay otros problemas más detallados que no he investigado (como por ejemplo, cómo los comodines son en realidad más poderosos que la varianza del sitio de uso), pero espero que esto responda a su pregunta sobre su contenido. Admitiré que soy parcial hacia la varianza del sitio de uso, pero intenté describir las principales ventajas de ambos que surgieron en mis discusiones con los progtwigdores y con los investigadores del lenguaje.

    La mayoría de la gente parece preferir la varianza del sitio de statement, porque hace que sea más fácil para los usuarios de la biblioteca (al tiempo que lo hace un poco más difícil para el desarrollador de la biblioteca, aunque yo sostengo que el desarrollador de la biblioteca debe pensar en la varianza independientemente en realidad está escrito.)

    Pero tenga en cuenta que ni Java ni C # son ejemplos de un buen diseño del lenguaje.

    Si bien Java tiene la varianza correcta y funciona independientemente de la JVM debido a las mejoras de VM compatibles en Java 5 y borrado de tipo, la varianza del sitio de uso hace que el uso sea un poco engorroso y la implementación particular de borrado de tipo ha merecido una merecida crítica.

    El modelo de la varianza del sitio de statement de C # quita la carga al usuario de la biblioteca, pero durante la introducción de los generics reificados básicamente construyeron las reglas de varianza en su máquina virtual. Incluso hoy en día no pueden respaldar completamente la co / contravariancia debido a este error (y la introducción no compatible con versiones anteriores de las clases de recolección reificadas ha dividido a los progtwigdores en dos campos).

    Esto plantea una restricción difícil en todos los lenguajes dirigidos al CLR y es una de las razones por la cual los lenguajes de progtwigción alternativos son mucho más activos en la JVM, aunque parece que el CLR tiene “características mucho más agradables”.

    Echemos un vistazo a Scala : Scala es un híbrido funcional completamente orientado a objetos que se ejecuta en la JVM. Usan borrado de tipos como Java, pero tanto la implementación de Generics como la varianza (declaratoria) son más fáciles de entender, más directas y poderosas que las de Java (o C # ‘s), porque la VM no impone reglas sobre cómo debe hacerlo la varianza. trabajo. El comstackdor de Scala comprueba las notaciones de varianza y puede rechazar el código fuente incorrecto en tiempo de comstackción en lugar de lanzar excepciones en el tiempo de ejecución, mientras que los archivos .class resultantes se pueden usar sin problemas desde Java.

    Una desventaja de la varianza del sitio de statement es que parece hacer que la inferencia de tipo sea más difícil en algunos casos.

    Al mismo tiempo, Scala puede usar tipos primitivos sin encajonarlos en colecciones como en C # utilizando la anotación @specialized que le dice al comstackdor de Scala que genere una o más implementaciones adicionales de una clase o método especializado para el tipo primitivo solicitado.

    Scala también puede “casi” reificar generics mediante el uso de manifiestos que les permite recuperar los tipos generics en tiempo de ejecución como en C #.

    Desventajas de los generics de estilo Java

    Una consecuencia es que la versión java solo funciona con tipos de referencia (o tipos de valores encuadrados) y no con tipos de valores. OMI es la mayor desventaja ya que previene los generics de alto rendimiento en muchos escenarios y requiere la escritura manual de tipos especializados.

    No garantiza un invariante como “Esta lista solo contiene objetos de tipo x” y necesita verificaciones de tiempo de ejecución en cada getter. El tipo genérico realmente existe.

    Al usar la reflexión, no puede preguntar a una instancia de un objeto genérico qué parámetros generics tiene.

    Ventajas de los generics de estilo Java

    Obtiene varianza / puede emitir entre diferentes parámetros generics.

    Java: generics de varianza del sitio de uso desde Java 5. Matrices covariantes rotas con una syntax diferente desde 1.0. No hay información de tipo de tiempo de ejecución de generics.

    C #: generics de varianza del sitio de uso desde C # 2.0. Se agregó una variación del sitio de statement en C # 4.0. Matrices covariantes rotas con una syntax diferente desde 1.0 (problema idéntico a Java). generics “reificados”, lo que significa que la información del tipo no se pierde en el momento de la comstackción.

    Scala: Tanto la varianza del sitio de uso como del sitio de statement desde las primeras versiones del lenguaje (al menos desde 2008). Las matrices no son una función de idioma separada, por lo que utiliza la misma syntax genérica y las reglas de varianza de tipo. Ciertas colecciones se implementan en el nivel de máquina virtual con matrices JVM para que obtenga un rendimiento igual o mejor en tiempo de ejecución en comparación con el código Java.

    Para profundizar en el problema de seguridad del tipo de matriz C # / Java: puede convertir un Perro [] en un Pet [] y agregar un Cat y desencadenar un error de tiempo de ejecución que no se detecta en el tiempo de comstackción. Scala lo implementó correctamente.