2012-09-11 17 views
8

Simplemente no obtengo algo en el tipo genérico de .NET. ¿Alguien puede explicar lo que sucede en el siguiente fragmento de código?Generics type casting

void Main() 
{ 
    IEnumerable<int> ints = new List<int>(); 
    IEnumerable<string> strings = new List<string>(); 

    var rez1=(IEnumerable<object>)ints; //runtime error 
    var rez2=(IEnumerable<object>)strings; //works 
    var rez3=(List<object>)strings; //runtime error 
} 

Respuesta

11

Vamos a empezar con la segunda línea que es más fácil.

que proyectan obras porque el parámetro de tipo de IEnumerable<T> es ahora covariant (eso es lo que el out en out T hace). Esto significa que puede lanzar un IEnumerable<Derived> a IEnumerable<Base> libremente.

La primera línea, que parecería ser el mismo caso, no funciona porque int es un tipo de valor. La varianza de la interfaz no funciona con tipos de valores porque los tipos de valores realmente no heredan de System.Object; pueden ser en caja en un object, pero eso no es lo mismo. La documentación menciona que

La varianza solo se aplica a los tipos de referencia; si especifica un tipo de valor para un parámetro de tipo de variante, ese parámetro de tipo es invariable para el tipo construido resultante .

Por último, la tercera línea no funciona porque el parámetro de tipo de List<T> es invariante . Puede ver que no hay out en su parámetro de tipo; las reglas no permitir que debido a List<T> no es una interfaz:

En el .NET Framework 4, parámetros de tipo de variante están restringidos a interfaz genérica y tipos de delegado genéricos.

+0

Pero Int32 se puede convertir en Objeto. ¿No es eso suficiente para hacer una covarianza? ¿O es una característica del compilador? – Vasaka

+0

@Vasaka: Es una operación de boxeo y no una conversión de referencia. Es técnico, pero a pesar de la sintaxis idéntica, lo que el compilador realmente hace en cada caso es totalmente diferente. – Jon

+0

... como son los parámetros de tipo de todas las clases y estructuras genéricas; type varianza soportada solo para interfaces y tipos de delegados. – phoog

0

La definición de cada tipo que se deriva de System.ValueType, con la excepción de System.Enum, en realidad define dos tipos de cosas: un tipo de objeto montón, y un tipo de almacenamiento en la ubicación. Las instancias de este último pueden convertirse implícitamente en el primero (haciendo una copia de los datos contenidos en él), y las instancias del primero pueden ser explícitamente encasilladas a este último (del mismo modo); a pesar de que ambos tipos de cosas se describen por el mismo System.Type, y aunque tienen los mismos miembros, se comportan de manera muy diferente.

A List<AnyClassType> esperará contener un montón de referencias de objetos de montón; si la lista en cuestión es List<String>, List<StringBuilder>, List<Button>, o lo que sea, puede ser de interés para los usuarios de la lista, pero realmente no interesa al List<T>. Si se lanza un List<Button> a un IEnumerable<Control>, alguien que llame a su método GetEnumerator() esperará obtener un objeto que generará referencias a los objetos del montón que derivan de Control; el retorno de List<Button>.GetEnumerator() satisfará esa expectativa.Por el contrario, si alguien lanzara List<Int32> a List<Object>, alguien que llamara GetEnumerator() esperaría algo que generaría referencias de objetos de montón, pero List<Integer>.GetEnumerator arrojará algo que emita enteros de tipo valor.

Es posible almacenar Int32 valores en un List<Object> o un List<ValueType>; almacenar un número entero en dicha lista lo convertirá en su forma de objeto de montón y almacenará una referencia a él; llamar al GetEnumerator() arrojaría algo que genere referencias de montón. Sin embargo, no hay forma de especificar que dicha lista solo contendrá instancias del tipo de pila correspondiente al Int32. En C++/CLI, es posible declarar variables del tipo "referencia al tipo de valor almacenado en el montón", pero los mecanismos detrás de los tipos genéricos en .net no pueden funcionar con dichos tipos.