2010-07-14 5 views
6

Ich habe eine Schnittstelle definiert, wie unten:C# Linq `Liste <Interface> .AddRange` Methode funktioniert nicht

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

Und zwei Linq-to-SQL-Klassen Umsetzung dieser Schnittstelle:

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

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

Ich habe IEnumerable Listen a und b durch den Datenbankeinträgen bevölkert von tblTestA und tblTestB

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

Howev er wird folgendes nicht erlaubt:

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

ich tun, wie folgt:

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

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

Gibt es etwas, was ich falsch mache? Danke für jede Hilfe

Antwort

8

Dies funktioniert in C# 4, wegen zu generische Kovarianz. Im Gegensatz zu früheren Versionen von C# gibt es eine Konvertierung von IEnumerable<tblTestA> zu IEnumerable<TestInterface>.

Die Funktionalität war in der CLR von v2, aber es wurde nur in C# 4 verfügbar gemacht (und die Framework-Typen nutzten es auch nicht vor .NET 4). Es gilt nur gilt für generische Schnittstellen und Delegaten (nicht Klassen) und nur für Referenztypen (so gibt es keine Konvertierung von IEnumerable<int> zu IEnumerable<object> zum Beispiel.) Es funktioniert auch nur dort, wo es sinnvoll ist - IEnumerable<T> ist kovariant als Objekte kommen nur "out "der API, während IList<T> ist Invariante, weil Sie Werte mit dieser API auch hinzufügen können.

Generische Kontravarianz wird auch unterstützt, funktioniert in der anderen Richtung - so können Sie zum Beispiel von IComparer<object> zu IComparer<string> konvertieren.

Wenn Sie nicht C# 4 verwenden, dann Tims Vorschlag der Verwendung Enumerable.Cast<T> ist ein guter - Sie verlieren ein wenig Effizienz, aber es wird funktionieren.

Wenn Sie mehr über generische Varianz lernen möchten, hat Eric Lippert eine long series of blog posts about it, und ich habe einen Vortrag darüber auf NDC 2010, die Sie auf der NDC video page sehen können.

6

Sie tun nichts falsch: List<TestInterface>.AddRange erwartet eine IEnumerable<TestInterface>. Es wird keine IEnumerable<tblTestA> oder IEnumerable<tblTestB> akzeptiert.

Ihre foreach Schleifen funktionieren. Alternativ können Sie Cast verwenden, um die Arten zu ändern:

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

+1 Vielen Dank für die Korrektur, sehr geschätzt – Jimbo

0

a und b sind vom Typ IEnumerable<tblTestA> und IEnumerable<tblTestB>
Während list.AddRange die Parameter erfordern vom Typ IEnumerable<TestInterface>

+0

Nun, sie verlangen, dass das Argument zu * IEnumerable * konvertierbar * Testschnittstelle> '. Ob das der Fall ist oder nicht hängt davon ab, welche Version von C# du verwendest ... –

+0

Ich vermute, du hast recht - ich kenne C# 4 nicht, Danke :) –

1

Der AddRange erwartet eine Liste von Schnittstellenobjekten, und Ihre "a" - und "b" -Variablen sind als eine Liste von abgeleiteten Klassenobjekten definiert. Offensichtlich scheint das vernünftig.NET könnte diesen Sprung logisch machen und sie als Listen der Schnittstellenobjekte behandeln, weil sie tatsächlich die Schnittstelle implementieren, diese Logik wurde einfach nicht in .NET bis 3.5 eingebaut.

Allerdings wurde diese Fähigkeit ("Kovarianz" genannt) zu .NET 4.0 hinzugefügt, aber bis Sie auf diese aktualisieren, werden Sie mit dem Schleifen bleiben, oder versuchen Sie ToArray() aufrufen und dann das Ergebnis an eine TaskInterface [], oder vielleicht eine LINQ-Abfrage, um jedes Element und eine neue Liste usw. zu erstellen.

Verwandte Themen