¿Por qué no se puede convertir IEnumerable en IEnumerable ?

¿Por qué la última línea no está permitida?

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

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

¿Eso significa que no hay forma de hacer que esto funcione?

 public interface IMyInterface { string Method(); } public class MyClass : IMyInterface { public string Method() { return "test"; } } public class Test { public static object test2() { IMyInterface a = new MyClass(); IMyInterface b = a; // Invalid cast! return b.Method(); } } 

Y que necesito escribir mi propio IMyInterface.Cast() para hacer eso?

¿Por qué la última línea no está permitida?

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 es porque el doble es un tipo de valor que no deriva de un objeto, por lo tanto, la covarianza no funciona?

No. El doble deriva de un objeto. Todos los tipos de valores se derivan del objeto.

Ahora la pregunta que deberías haber hecho:

¿Por qué la covarianza no funciona para convertir IEnumerable a IEnumerable ?

Porque, ¿ quién hace el boxeo ? Una conversión de doble a objeto debe contener el doble. Supongamos que tiene una llamada a IEnumerator.Current es “realmente” una llamada a una implementación de IEnumerator.Current . La persona que llama espera que se devuelva un objeto. El destinatario devuelve un doble. ¿Dónde está el código que realiza la instrucción de boxeo que convierte el doble devuelto por IEnumerator.Current en un doble IEnumerator.Current ?

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

Si desea que se ejecute el código de las casillas, debe escribirse en algún momento y usted es la persona que puede escribirlo. La forma más fácil es utilizar el método de extensión Cast :

 IEnumerable objects2 = doubleenumerable.Cast(); 

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

ACTUALIZACIÓN: Un comentarista nota que he rogado 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 para resolver el problema de saber si boxear o no?

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

 public static IEnumerable Cast(this IEnumerable sequence) { if (sequence == null) throw ... if (sequence is IEnumerable) return sequence as IEnumerable; return ReallyCast(sequence); } private static IEnumerable ReallyCast(IEnumerable sequence) { foreach(object item in sequence) yield return (T)item; } 

La responsabilidad de determinar si la conversión de objeto a T es una conversión de 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.

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 se copia una variable de tipo Double a uno de tipo Object , se crea una nueva instancia del tipo de objeto de clase asociado con Double y se copian todos los bytes del original a ese nuevo objeto de clase. Aunque el tipo de clase Double encajonado tiene el mismo nombre que el tipo de valor Double , esto no genera ambigüedad porque generalmente 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 dichas 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 a GetType en una estructura se describe a menudo en términos de que GetType es 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.

La varianza de este tipo solo se admite para tipos de referencia. Ver http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx