2012-03-13 21 views
27

¿Por qué no se permite la última línea?¿Por qué no puedo incluir IEnumerable <struct> como IEnumerable <object>?

IEnumerable<double> doubleenumerable = new List<double> { 1, 2 }; 
IEnumerable<string> stringenumerable = new List<string> { "a", "b" }; 
IEnumerable<object> objects1 = stringenumerable; // OK 
IEnumerable<object> objects2 = doubleenumerable; // Not allowed 

Se debe esto a doble es un tipo de valor que no se deriva de objeto, por lo tanto, la covarianza no funciona?

¿Eso significa que no hay manera de hacer este trabajo:

public interface IMyInterface<out T> 
{ 
    string Method(); 
} 

public class MyClass<U> : IMyInterface<U> 
{ 
    public string Method() 
    { 
     return "test"; 
    } 
} 

public class Test 
{ 
    public static object test2() 
    { 
     IMyInterface<double> a = new MyClass<double>(); 
     IMyInterface<object> b = a; // Invalid cast! 
     return b.Method(); 
    } 
} 

y que tengo que escribir mi propio IMyInterface<T>.Cast<U>() de hacer eso?

+0

posible duplicado de [Por qué la covarianza y la contravarianza no admiten el tipo de valor] (http://stackoverflow.com/questions/12454794/why-covariance-and-contravariance-do-not-support-value-type) – nawfal

Respuesta

44

¿Por qué no se permite la última línea?

Porque el doble es un tipo de valor y el objeto es un tipo de referencia; la covarianza solo funciona cuando ambos tipos son tipos de referencia.

¿Esto se debe a que el doble es un tipo de valor que no se deriva del objeto, por lo tanto, la covarianza no funciona?

No. Double does derive from object. Todos los tipos de valores se derivan del objeto.

Ahora la pregunta que debería haber preguntado:

¿Por qué no funciona covarianza para convertir IEnumerable<double> a IEnumerable<object>?

Porque que hace el boxeo? Una conversión de doble a objeto debe box el doble. Supongamos que tiene una llamada al IEnumerator<object>.Current que es "realmente" una llamada a una implementación de IEnumerator<double>.Current. La persona que llama espera que se devuelva un objeto. El destinatario devuelve un doble. ¿Dónde está el código que hace la instrucción de boxeo que convierte el doble devuelto por IEnumerator<double>.Current en un doble en caja?

Es en ninguna parte, ahí es donde, y es por eso que esta conversión es ilegal. La llamada al Current va a poner un doble de ocho bytes en la pila de evaluación, y el consumidor va a esperar una referencia de cuatro bytes a un doble en la pila de evaluación, por lo que el consumidor se bloqueará y morirá horriblemente con una pila desalineada y una referencia a la memoria no válida.

Si desea que el código que se ejecutará cajas a entonces tiene que haber escrito en algún momento, y usted es la persona que recibe a escribirlo. La forma más sencilla es utilizar el método Cast<T> extensión:

IEnumerable<object> objects2 = doubleenumerable.Cast<object>(); 

Ahora se llama a un método de ayuda que contiene la instrucción de boxeo que convierte el doble de una de ocho bytes doble a una referencia.

ACTUALIZACIÓN: Un comentarista nota que he planteado la pregunta, es decir, he respondido una pregunta presuponiendo la existencia de un mecanismo que resuelve un problema tan difícil como lo requiere una solución a la pregunta original. ¿Cómo se logra la implementación de Cast<T> para resolver el problema de saber si boxear o no?

Funciona como este boceto. Tenga en cuenta que los tipos de parámetros son no genérico:

public static IEnumerable<T> Cast<T>(this IEnumerable sequence) 
{ 
    if (sequence == null) throw ... 
    if (sequence is IEnumerable<T>) 
     return sequence as IEnumerable<T>; 
    return ReallyCast<T>(sequence); 
} 

private static IEnumerable<T> ReallyCast<T>(IEnumerable sequence) 
{ 
    foreach(object item in sequence) 
     yield return (T)item; 
} 

La responsabilidad de determinar si el reparto de un objeto a T es una conversión unboxing o una conversión de referencia se difiere al tiempo de ejecución. La fluctuación de fase sabe si T es un tipo de referencia o un tipo de valor. El 99% del tiempo será, por supuesto, un tipo de referencia.

+6

Off tema, pero me gustaría que tuiteara. – Joe

+1

1+ Como de costumbre, más rápido con la publicación. Me gusta "para que el consumidor se bloquee y muera horriblemente con una pila desalineada y una referencia a la memoria no válida". –

+17

@JoeTuskan: Cuando tenga algo que decir que se ajuste a 120 caracteres y sea de interés general, lo haré. Espera una larga espera. –

4

Para comprender lo que está permitido y lo que no está permitido, y por qué las cosas se comportan como lo hacen, es útil comprender lo que sucede bajo el capó. Para cada tipo de valor, existe un tipo correspondiente de objeto de clase, que, como todos los objetos, heredará de System.Object. Cada objeto de clase incluye con sus datos una palabra de 32 bits (x86) o una palabra larga de 64 bits (x64) que identifica su tipo. Las ubicaciones de almacenamiento de tipo valor, sin embargo, no contienen dichos objetos de clase o referencias a ellas, ni tienen una palabra de tipo datos almacenados con ellos. En cambio, cada ubicación de tipo de valor primitivo simplemente contiene los bits necesarios para representar un valor, y cada ubicación de almacenamiento de tipo de valor de estructura simplemente contiene los contenidos de todos los campos públicos y privados de ese tipo.

Cuando uno copias una variable de tipo Double a uno de tipo Object, se crea una nueva instancia del tipo de clase de objeto asociado a Double y copia todos los bytes desde el original a la nueva clase de objeto. Aunque el tipo de clase Double en caja tiene el mismo nombre que el tipo de valor Double, esto no genera ambigüedad porque, en general, no se pueden usar en los mismos contextos. Las ubicaciones de almacenamiento de tipos de valor contienen bits sin procesar o combinaciones de campos, sin información de tipo almacenada; al copiar una de esas ubicaciones de almacenamiento, se copian todos los bytes y, en consecuencia, se copian todos los campos públicos y privados. Por el contrario, los objetos de montón de tipos derivados de tipos de valores son objetos de montón y se comportan como objetos de montón. Aunque C# se refiere a los contenidos de las ubicaciones de almacenamiento de tipo valor como si fueran derivados de Object, bajo el capó el contenido de tales ubicaciones de almacenamiento son simplemente colecciones de bytes, efectivamente fuera del sistema de tipo. Como solo se puede acceder a ellos mediante un código que sepa lo que representan los bytes, no es necesario almacenar dicha información en la ubicación de almacenamiento. Aunque la necesidad de boxeo al llamar al GetType en una estructura se describe a menudo en términos de GetType como una función no virtual no sombreada, la necesidad real proviene del hecho de que el contenido de una ubicación de almacenamiento de tipo valor (a diferencia de la ubicación en sí) no tienen información de tipo.

Cuestiones relacionadas