2010-07-14 16 views
6

He una interfaz definida de la siguiente manera:C# LINQ `Lista <Interface> .AddRange` método no funciona

public interface TestInterface{ 
    int id { get; set; } 
} 

Y dos clases de LINQ a SQL incorporación de dicha interfaz:

public class tblTestA : TestInterface{ 
    public int id { get; set; } 
} 

public class tblTestB : TestInterface{ 
    public int id { get; set; } 
} 

tengo listas IEnumerable a y b poblada por los registros de base de datos de tblTestA y tblTestB

IEnumerable<tblTestA> a = db.tblTestAs.AsEnumerable(); 
IEnumerable<tblTestB> b = db.tblTestBs.AsEnumerable(); 

Howev er, no se permite lo siguiente:

List<TestInterface> list = new List<TestInterface>(); 
list.AddRange(a); 
list.AddRange(b); 

que tengo que hacer de la siguiente manera:

foreach(tblTestA item in a) 
    list.Add(item) 

foreach(tblTestB item in b) 
    list.Add(item) 

¿Hay algo que estoy haciendo mal? Gracias por cualquier ayuda

Respuesta

8

Esto funciona en C# 4, debido a covarianza genérica. A diferencia de las versiones anteriores de C#, hay una conversión de IEnumerable<tblTestA> a IEnumerable<TestInterface>.

La funcionalidad ha estado en el CLR desde v2, pero solo ha sido expuesta en C# 4 (y los tipos de framework tampoco lo aprovecharon antes de .NET 4). Es solo se aplica a interfaces genéricas y delegados (no clases) y solo para tipos de referencia (por lo que no hay conversión de IEnumerable<int> a IEnumerable<object> por ejemplo). También solo funciona donde tiene sentido - IEnumerable<T> es covariante ya que los objetos solo salen " "de la API, mientras que IList<T> es invariante porque también puede agregar valores con esa API.

La contravarianza genérica también es compatible, trabajando en la otra dirección, por lo que puede convertir de IComparer<object> a .

Si no está usando C# 4, la sugerencia de Tim de usar Enumerable.Cast<T> es buena: pierde un poco de eficiencia, pero funcionará.

Si desea obtener más información sobre la varianza genérica, Eric Lippert tiene un long series of blog posts about it, y di una charla sobre ello en NDC 2010 que puede ver en el NDC video page.

6

No está haciendo nada incorrecto: List<TestInterface>.AddRange espera un IEnumerable<TestInterface>. No aceptará un IEnumerable<tblTestA> o IEnumerable<tblTestB>.

Sus bucles foreach funcionan. Como alternativa, se puede usar Cast para cambiar los tipos:

List<TestInterface> list = new List<TestInterface>(); 
list.AddRange(a.Cast<TestInterface>()); 
list.AddRange(b.Cast<TestInterface>()); 
+0

+1 Gracias por la corrección, muy apreciada – Jimbo

0

a y b son de tipo IEnumerable<tblTestA> y IEnumerable<tblTestB>
Mientras list.AddRange requieren el parámetro a ser de tipo IEnumerable<TestInterface>

+0

Bueno, requieren que el argumento sea * convertible * a 'IEnumerable < TestInterface> '. Si ese es el caso o no depende de la versión de C# que está usando ... –

+0

I Supongo que tiene razón - No estoy familiarizado con C# 4, Gracias :) –

1

El AddRange está esperando una lista de objetos de la interfaz, y su "a" y "b" varaibles se define para ser una lista de objetos de clases derivadas. Obviamente, parece razonable que.NET podría lógicamente hacer ese salto y tratarlos como listas de los objetos de la interfaz porque realmente implementan la interfaz, esa lógica simplemente no era construir en .NET hasta 3.5.

Sin embargo, esta capacidad (llamada "covarianza") se ha agregado a .NET 4.0, pero hasta que se actualice a eso, se encontrará con bucles, o tal vez intente llamar a ToArray() y luego lanzar el resultado a una TareaInterfaz [], o tal vez una consulta LINQ para identificar cada elemento y crear una nueva lista, etc.

Cuestiones relacionadas