2010-07-14 1 views
6

J'ai une interface définie comme ci-dessous:C# Linq `Liste <Interface> .AddRange` Méthode ne fonctionne pas

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

Et deux LINQ to SQL classes implémentant cette interface:

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

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

Je listes IEnumerable a et b peuplé par les enregistrements de la base de tblTestA et tblTestB

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

Howev er, ce qui suit est interdit:

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

que je dois faire comme suit:

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

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

Y at-il quelque chose que je fais mal? Merci pour toute aide

Répondre

8

Cela fonctionne en C# 4, en raison à covariance générique. Contrairement aux versions précédentes de C#, il existe une conversion de IEnumerable<tblTestA> à IEnumerable<TestInterface>.

La fonctionnalité a été dans le CLR à partir de v2, mais elle a seulement été exposée dans C# 4 (et les types de framework n'en ont pas profité avant .NET 4 non plus). Il seulement s'applique aux interfaces génériques et délégués (pas classes) et seulement pour les types de référence (donc il n'y a pas de conversion de IEnumerable<int> à IEnumerable<object> par exemple.) Cela fonctionne aussi seulement quand ça a du sens - IEnumerable<T> est covariant car les objets sortent seulement "de l'API, alors que IList<T> est invariant car vous pouvez aussi ajouter des valeurs avec cette API.

La contravariance générique est également prise en charge, en travaillant dans l'autre sens - par exemple, vous pouvez convertiren IComparer<string>.

Si vous n'utilisez pas C# 4, alors la suggestion de Tim d'utiliser Enumerable.Cast<T> est bonne - vous perdez un peu d'efficacité, mais cela fonctionnera. Si vous voulez en savoir plus sur la variance générique, Eric Lippert a un long series of blog posts about it, et j'en ai parlé au NDC 2010 que vous pouvez visionner sur le NDC video page.

6

Vous ne faites rien de mal: List<TestInterface>.AddRange attend un IEnumerable<TestInterface>. Il n'acceptera pas de IEnumerable<tblTestA> ou de IEnumerable<tblTestB>. Les lignes foreach fonctionnent. Sinon, vous pouvez utiliser Cast changer les types:

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

+1 Merci pour le correctif, très apprécié – Jimbo

0

a et b sont de type IEnumerable<tblTestA> et IEnumerable<tblTestB>
Alors que list.AddRange exigent le paramètre à être de type IEnumerable<TestInterface>

+0

Eh bien, ils nécessitent que l'argument soit * convertible * en 'IEnumerable < TestInterface> '. Que ce soit le cas ou non dépend de la version de C# que vous utilisez ... –

+0

Je suppose que vous avez raison - je ne suis pas familier avec C# 4, Merci :) –

1

L'AddRange attend une liste d'objets d'interface, et vos variables "a" et "b" sont définies pour être une liste d'objets de classe dérivés. Évidemment, cela semble raisonnable.NET pourrait logiquement faire ce saut et les traiter comme des listes d'objets d'interface car ils implémentent en effet l'interface, cette logique n'étant tout simplement pas construite en .NET jusqu'à 3.5. Cependant, cette capacité (appelée "covariance") a été ajoutée à .NET 4.0, mais jusqu'à ce que vous mettiez à niveau, vous serez bloqué par une boucle, ou peut-être essayez d'appeler ToArray(), puis de convertir le résultat en une TaskInterface [], ou peut-être une requête LINQ pour classer chaque élément et créer une nouvelle liste, etc.

Questions connexes