versiones sharp pagina oficial official net historia guía .net-4.0 c#-4.0 covariance contravariance generic-variance

.net 4.0 - sharp - Varianza genérica en C#4.0



historia del c# (3)

La varianza genérica en C # 4.0 se ha implementado de tal manera que es posible escribir lo siguiente sin excepción (que es lo que sucedería en C # 3.0):

List<int> intList = new List<int>(); List<object> objectList = intList;

[Ejemplo no funcional: ver la respuesta de Jon Skeet]

Hace poco asistí a una conferencia en la que Jon Skeet ofreció un excelente resumen de la Varianza genérica, pero no estoy seguro de que lo esté entendiendo por completo. Entiendo el significado de las palabras clave de out y out cuando se trata de contra y covarianza. Pero tengo curiosidad por lo que pasa detrás de escena.

¿Qué ve el CLR cuando se ejecuta este código? ¿Está convirtiendo implícitamente la List<int> en List<object> o simplemente está integrado en que ahora podemos convertir entre tipos derivados a tipos principales?

Por interés, ¿por qué no se introdujo esto en versiones anteriores y cuál es el principal beneficio, es decir, el uso en el mundo real?

Más información en esta post para Varianza genérica (pero la pregunta está extremadamente desactualizada, en busca de información real y actualizada)


Por interés, ¿por qué no se introdujo en versiones anteriores?

Las primeras versiones (1.x) de .NET no tenían genéricos en absoluto, por lo que la variación genérica estaba muy lejos.

Cabe señalar que en todas las versiones de .NET, hay covarianza de matriz. Desafortunadamente, es una covarianza insegura:

Apple[] apples = new [] { apple1, apple2 }; Fruit[] fruit = apples; fruit[1] = new Orange(); // Oh snap! Runtime exception! Can''t store an orange in an array of apples!

La co y contra-varianza en C # 4 es segura, y previene este problema.

¿Cuál es el principal beneficio, es decir, el uso en el mundo real?

Muchas veces en el código, al llamar a una API se espera un tipo de Base amplificada (por ejemplo, IEnumerable<Base> ) pero todo lo que tienes es un tipo de Derived amplificado (por ejemplo, IEnumerable<Derived> ).

En C # 2 y C # 3, necesitaría convertir manualmente a IEnumerable<Base> , aunque debería "simplemente funcionar". Co y contra-varianza hace que sea "simplemente un trabajo".

ps apesta totalmente que la respuesta de Skeet es comerse todos mis puntos de reputación. ¡Maldito seas, Skeet! :-) Parece que ha respondido esto antes , sin embargo.


No, tu ejemplo no funcionaría por tres razones:

  • Las clases (como List<T> ) son invariantes; Solo los delegados y las interfaces son variantes.
  • Para que la varianza funcione, la interfaz solo tiene que usar el parámetro type en una dirección (in for contravariance, out for covariance)
  • Los tipos de valor no se admiten como argumentos de tipo para la variación, por lo que no hay IEnumerable<int> de IEnumerable<int> a IEnumerable<object> por ejemplo

(El código no se compila tanto en C # 3.0 como en 4.0 - no hay excepción).

Así que esto funcionaría:

IEnumerable<string> strings = new List<string>(); IEnumerable<object> objects = strings;

El CLR solo usa la referencia, sin cambios, no se crean nuevos objetos. Por lo tanto, si llama a objects.GetType() , aún así obtendrá la List<string> .

Creo que no se introdujo antes porque los diseñadores de idiomas aún tenían que resolver los detalles de cómo exponerlo: ha estado en el CLR desde la v2.

Los beneficios son los mismos que en otras ocasiones en las que desea poder utilizar un tipo como otro. Para usar el mismo ejemplo que usé el sábado pasado, si tienes algo, implementa IComparer<Shape> para comparar formas por área, es una locura que no puedas usar eso para ordenar una List<Circle> - si puede comparar dos formas, sin duda puede comparar cualquiera de los dos círculos. A partir de C # 4, habría una conversión contravariante de IComparer<Shape> a IComparer<Circle> por lo que podría llamar a los circles.Sort(areaComparer) .


Unos cuantos pensamientos adicionales.

¿Qué ve el CLR cuando se ejecuta este código?

Como Jon y otros han señalado correctamente, no estamos haciendo variaciones en las clases, solo interfaces y delegados. Entonces en tu ejemplo, el CLR no ve nada; ese código no se compila. Si lo obliga a compilar insertando suficientes lanzamientos, se bloquea en el tiempo de ejecución con una excepción de conversión incorrecta.

Ahora, todavía es una pregunta razonable preguntarse cómo funciona la variación detrás de la escena cuando funciona. La respuesta es: la razón por la que estamos restringiendo esto a los argumentos de tipo de referencia que parametrizan los tipos de interfaz y delegado es para que no suceda nada detrás de la escena. Cuando tu dices

object x = "hello";

Lo que sucede detrás de la escena es que la referencia a la cadena está pegada en la variable de tipo objeto sin modificación . Los bits que forman una referencia a una cadena son bits legales para ser una referencia a un objeto, por lo que no es necesario que ocurra nada aquí. El CLR simplemente deja de pensar que esos bits se refieren a una cadena y comienza a pensar que se refieren a un objeto.

Cuando tu dices:

IEnumerator<string> e1 = whatever; IEnumerator<object> e2 = e1;

La misma cosa. No pasa nada. Los bits que hacen referencia a un enumerador de cadenas son los mismos que hacen referencia a un enumerador de objetos. Hay algo más de magia que entra en juego cuando haces un reparto, por ejemplo:

IEnumerator<string> e1 = whatever; IEnumerator<object> e2 = (IEnumerator<object>)(object)e1;

Ahora el CLR debe generar una verificación de que e1 implementa esa interfaz, y esa verificación debe ser inteligente para reconocer la varianza.

Pero la razón por la que podemos evitar que las interfaces variantes sean simplemente conversiones sin operación es porque la compatibilidad de la asignación regular es así. ¿Para qué vas a usar e2?

object z = e2.Current;

Eso devuelve los bits que son una referencia a una cadena. Ya hemos establecido que son compatibles con objeto sin cambio.

¿Por qué no se introdujo esto antes? Teníamos otras características que hacer y un presupuesto limitado.

¿Cuál es el principio de beneficio? Que las conversiones de secuencia de cadena a secuencia de objeto "simplemente funcionan".