2008-11-25 3 views
7

Ich verstehe, dass, wenn S eine Kindklasse von T ist, dann ein List<S> ist nicht ein Kind von List<T>. Fein. Aber Interfaces haben ein anderes Paradigma: Wenn Foo implementiert IFoo, warum ist dann ein List<Foo> nicht (ein Beispiel für) ein List<IFoo>?Warum kann ich keine Liste <Foo> zurückgeben, wenn Sie nach einer Liste <IFoo> gefragt werden?

Da es keine tatsächliche Klasse IFoo geben kann, bedeutet das, dass ich jedes Element der Liste immer aussetzen müsste, wenn ich eine List<IFoo> aussetze? Oder ist das einfach schlechtes Design und ich muss meine eigene Collection-Klasse ListOfIFoos definieren, um mit ihnen arbeiten zu können? Keine scheint mir vernünftig ...

Was wäre der beste Weg, eine solche Liste zu entlarven, da ich versuche, Schnittstellen zu programmieren? Ich tendiere derzeit dazu, meine List<Foo> intern als List<IFoo> zu speichern.

+0

Um die zugrunde liegenden Probleme dieser Frage in Bezug auf das generalisierte Problem der Typumwandlung in .NET zu beheben, lesen Sie bitte meine Antwort auf eine verwandte Frage unter [Wie kann ich Typen zur Laufzeit konvertieren?] (Http://stackoverflow.com/ Fragen/312858/how-can-ich-Convert-Typen-zur Laufzeit/7942350 # 7942350) –

Antwort

14

Ihre List<Foo> ist keine Unterklasse, wenn List<IFoo> weil Sie keine MyOwnFoo Objekt darin speichern kann, die auch eine IFoo Implementierung sein geschieht. (Liskov substitution principle)

Die Idee der Speicherung einer List<IFoo> anstelle einer dedizierten List<Foo> ist in Ordnung. Wenn Sie den Inhalt der Liste in den Implementierungstyp umwandeln müssen, bedeutet dies wahrscheinlich, dass Ihre Schnittstelle nicht geeignet ist.

5

MASSIVE EDIT Sie werden in der Lage es mit C# 4.0 zu tun, aber [Dank Jon]

können Sie erhalten um es mit ConvertAll:

public List<IFoo> IFoos() 
{ 
    var x = new List<Foo>(); //Foo implements IFoo 
    /* .. */ 
    return x.ConvertAll<IFoo>(f => f); //thanks Marc 
} 
+0

Wenn ich es nicht missverstanden habe, nein, wirst du nicht. Eine Liste kann weder gleich- noch kontravariabel sein, da sie sowohl "in" als auch "out" erlaubt. –

+0

+1 für Marcs Kommentar. –

+0

Sie müssten auch den Zieltyp qualifizieren: return x.ConvertAll (f => f); –

8

In Ihrer Rückkehr Funktion, müssen Sie die Liste eine Liste von Schnittstellen machen, und wenn Sie das Objekt erstellen, machen Sie es als ein Objekt, das es implementiert. Wie folgt aus:

function List<IFoo> getList() 
{ 
    List<IFoo> r = new List<IFoo>(); 
    for(int i=0;i<100;i++) 
     r.Add(new Foo(i+15)); 

    return r; 
} 
0

Die einfache Antwort ist, dass List<Foo> eine andere Art zu List<IFoo> ist, auf die gleiche Art und Weise, dass DateTime-IPAddress unterschiedlich ist, zum Beispiel.

jedoch die Tatsache, dass Sie IFoo haben bedeutet, dass Sammlungen von IFoo zu erwarten sind mindestens zwei Implementierungen von IFoo (FooA, FooB, etc ...) enthalten, denn wenn man es erwartet jemals nur eine Implementierung von IFoo sein , Foo, dann ist der Typ IFoo redundant.

Also, wenn es immer nur einen abgeleiteten Typ einer Schnittstelle geben wird, vergessen Sie die Schnittstelle und sparen Sie den Overhead. Wenn zwei oder mehr abgeleitete Typen einer Schnittstelle vorhanden sind, verwenden Sie immer den Schnittstellentyp in Sammlungen/generischen Parametern.

Wenn Sie Thunkcode schreiben, dann gibt es wahrscheinlich irgendwo einen Designfehler.

+0

Es gibt andere Gründe für die Verwendung von Schnittstellen als nur wenn ich beabsichtige, dass es verschiedene Implementierungen gibt. –

12

Hier ist ein Beispiel dafür, warum Sie es nicht tun:

// Suppose we could do this... 
public List<IDisposable> GetDisposables() 
{ 
    return new List<MemoryStream>(); 
} 

// Then we could do this 
List<IDisposable> disposables = GetDisposables(); 
disposables.Add(new Form()); 

An diesem Punkt eine Liste, die MemoryStreams erstellt wurde nun ein Formular in ihm zu halten. Schlecht!

Im Grunde ist diese Einschränkung vorhanden, um die Typensicherheit zu gewährleisten. In C# 4 und .NET 4.0 wird es begrenzte Unterstützung für diese (es heißt Varianz), aber es wird immer noch nicht unterstützt dieses besondere Szenario, genau aus den oben genannten Gründen.

+0

Ich verstehe das wirklich nicht. Die Liste wurde erstellt, um IDisposables und nicht MemoryStreams zu enthalten. Sie könnten die gleiche Art mit einer Liste tun, und der Compiler ließ Sie. Was ist anders an Schnittstellen? –

+0

"Der Compiler * lässt * Sie." –

+0

Nein, dies ist mit der Liste nicht möglich.Versuchen Sie, eine Liste als Liste zurückzugeben - Sie werden sehen, dass sie fehlschlägt. –

0

Wenn zu dem Zeitpunkt, zu IList<T> erfunden wurde, hatte Microsft bewusst, dass zukünftige Versionen von .NET-Schnittstelle Kovarianz und Kontra unterstützen würden, wäre es möglich gewesen, und nützlich, um die Schnittstelle in IReadableList<out T> zu spalten, IAppendable<in T> und IList<T> die würde beide der oben genannten erben. Dies hätte den vb.net-Implementierern eine kleine Menge zusätzlicher Arbeit auferlegt (sie müssten sowohl die schreibgeschützte als auch die schreibgeschützte Version der indizierten Eigenschaft definieren, da .NET aus irgendeinem Grund kein Lesen-Schreiben zulässt Eigenschaft, die als schreibgeschützte Eigenschaft dienen soll, würde aber bedeuten, dass Methoden, die Elemente aus einer Liste einfach lesen müssen, IReadableList<T> kovariant empfangen können und Methoden, die einfach eine Sammlung benötigen, an die sie anhängen können, eine IAppendable<T> in kontravarianter Form erhalten Mode. Die einzige Möglichkeit, wie eine solche Sache heute implementiert werden könnte, wäre leider, wenn Microsoft ein Mittel für neue Schnittstellen zur Verfügung stünde, die für ältere austauschbar wären, wobei Implementierungen der alten Schnittstellen automatisch Standardmethoden verwenden, die von den neuen bereitgestellt werden. Ich würde denken, dass ein solches Feature (Austauschbarkeit der Benutzeroberfläche) sehr hilfreich wäre, aber ich würde meinen Atem nicht anhalten und darauf warten, dass Microsoft es implementiert.

Da es keine Möglichkeit gibt, IReadableList<T> in IList<T> zu hinterlegen, wäre ein alternativer Ansatz, die eigene listenbezogene Schnittstelle zu definieren. Die einzige Schwierigkeit dabei ist, dass alle Instanzen von System.Collections.Generic.List<T> durch einen anderen Typ ersetzt werden müssen, obwohl die Schwierigkeit, dies zu tun, minimiert werden könnte, wenn man eine List<T> Struktur in einem anderen Namensraum definieren würde, der ein einzelnes System.Collections.Generic.List<T> Feld enthält Definierte Erweiterungskonvertierungen zum und vom Systemtyp (mit einer Struktur statt einer Klasse würde bedeuten, dass Code es überflüssig macht, neue Heap-Objekte beim Casting in einem Szenario zu erstellen, in dem die Struktur nicht eingerahmt werden müsste).

+0

Ich würde sehr vorsichtig sein, etwas so grundlegendes neu zu definieren wie Liste ; Die Wahrscheinlichkeit, dass jemand verwirrt wird, liegt bei 100%, vermute ich. –