2010-05-06 16 views
36

En el siguiente fragmento de código esperaba poder convertir implícitamente de elements a baseElements porque TBase es implícitamente convertible a IBase.¿Es esto un error de covarianza en C# 4?

public interface IBase { } 
public interface IDerived : IBase { } 
public class VarianceBug 
{ 
    public void Foo<TBase>() where TBase : IBase 
    { 
     IEnumerable<TBase> elements = null; 
     IEnumerable<IDerived> derivedElements = null; 
     IEnumerable<IBase> baseElements; 

     // works fine 
     baseElements = derivedElements; 

     // error CS0266: Cannot implicitly convert type 
     // 'System.Collections.Generic.IEnumerable<TBase>' to 
     // 'System.Collections.Generic.IEnumerable<IBase>'. 
     // An explicit conversion exists (are you missing a cast?) 
     baseElements = elements; 
    } 
} 

Sin embargo, me sale el error que se menciona en el comentario.

Citando de la especificación:

Un tipo T<A1, …, An> es de varianza-convertible a un tipo T<B1, …, Bn> si T es o bien una interfaz o un tipo de delegado declarado con los parámetros de tipo variante T<X1, …, Xn>, y para cada parámetro de tipo de variante Xi una de las siguientes bodegas:

  • Xi es covariante y existe una referencia implícita o conversión identidad de Ai a Bi

  • Xi es contravariant y existe una referencia implícita o conversión identidad Bi-Ai

  • Xi es invariante y existe una conversión de identidad a partir de Ai a Bi

Comprobación mi código, parece ser consistente con la especificación:

  • IEnumerable<out T> es un tipo de interfaz

  • IEnumerable<out T> se declara con parámetros de tipo variante

  • T es covariante existe

  • una conversión de referencia implícita TBase-IBase

Entonces, ¿es un error en el compilador C# 4?

+0

Qué pasa cuando se desechan de manera explícita? El compilador dice que hay uno. Ya que estás en una postura negativa, ¿tiene sentido ...? – flq

+2

Solo para hacerlo explícito: es su última viñeta "existe una conversión de referencia implícita de TBase a IBase" que no es cierta (a menos que agregue ': clase'). Puede ser asignable, pero * no * necesariamente es una conversión de referencia. Sin ': class', se trata de una conversión" restringida ", que es algo mágico que permite que los mismos métodos de invocación de IL (incluidos los accesores de propiedad) en tipos de referencia y tipos de valor de la misma manera: http://msdn.microsoft .com/en-us/library/system.reflection.emit.opcodes.constrained.aspx –

+0

Charles: está equivocado: la primera asignación funciona (Works on My Machine (TM)). –

Respuesta

49

La varianza solo funciona para los tipos de referencia (o hay una identidad conversión). No se sabe que es TBase tipo de referencia, a menos que agregue : class:

public void Foo<TBase>() where TBase : class, IBase 

ya que podría escribir una:

public struct Evil : IBase {} 
+0

Buena respuesta: agregar restricciones de clase funciona. Sin embargo, esto plantea otra pregunta: ¿por qué funciona la primera asignación? –

+3

@Omer - porque 'IBase' y' IDerived' * se * tratan como referencias; es solo 'TBase' lo que está indeciso. –

+0

@MarcGravell: Para ser más precisos, mientras que las interfaces pueden implementarse mediante tipos no de almacenamiento (valor), una ubicación de almacenamiento de un tipo de interfaz * siempre * mantendrá una referencia de objeto de almacenamiento dinámico. Por ejemplo, un 'List >' contiene referencias a los objetos heap que implementan 'IEnumerator ', mientras que 'List donde T: IEnumerator ' sería, si 'T' es' List .Enumerator', contiene instancias de ese tipo de estructura en su lugar. Tenga en cuenta que si uno intenta almacenar un 'List .Enumerator' en una' List > ', el sistema copiará sus campos en un nuevo objeto heap y almacenará una referencia a eso. – supercat

13

Marc es correcta - Estaba a punto de pegar la misma respuesta.

Ver la covarianza & contravarianza FAQ:

http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

Desde el FAQ:

"se apoya Varianza sólo si un parámetro de tipo es un tipo de referencia."

varianza no es compatible con los tipos de valor

La siguiente no se compila ya sea:

// int is a value type, so the code doesn't compile. 
IEnumerable<Object> objects = new List<int>(); // Compiler error here. 
+0

Buen enlace de soporte - gracias –

+0

Y más en línea con la pregunta, el siguiente tampoco funcionará: 'IEnumerable comparables = new List ();' –

Cuestiones relacionadas