2010-08-25 14 views
9

Ich habe ein Problem zu verstehen, wie Polymorphie funktioniert bei der Verwendung von Generika. Als Beispiel habe ich das folgende Programm definiert:Bitte helfen Sie mir zu verstehen, Polymorphismus bei der Verwendung von Generika in C#

public interface IMyInterface 
{ 
    void MyMethod(); 
} 

public class MyClass : IMyInterface 
{ 
    public void MyMethod() 
    { 
    } 
} 

public class MyContainer<T> where T : IMyInterface 
{ 
    public IList<T> Contents; 
} 

Ich kann dies dann tun, was ganz gut funktioniert:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 

ich viele Klassen, die MyInterface implementieren. Ich möchte eine Methode schreiben, die alle MyContainer Objekte annehmen können:

public void CallAllMethodsInContainer(MyContainer<IMyInterface> container) 
{ 
    foreach (IMyInterface myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

Nun, ich möchte diese Methode nennen.

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

Das hat nicht funktioniert. Da MyClass IMyInterface implementiert, sollte ich es einfach umsetzen können?

Das hat auch nicht funktioniert. Ich kann auf jeden Fall eine normale MyClass IMyInterface Stimmen:

MyClass newClass = new MyClass(); 
IMyInterface myInterface = (IMyInterface)newClass; 

So zumindest habe ich nicht ganz, dass falsch verstanden. Ich bin mir nicht ganz sicher, wie ich eine Methode schreiben soll, die eine generische Sammlung von Klassen akzeptiert, die der gleichen Schnittstelle entsprechen.

Ich habe einen Plan, um dieses Problem vollständig zu hacken, wenn es sein muss, aber ich würde es wirklich vorziehen, es richtig zu machen.

Vielen Dank im Voraus.

+2

Hier trotzen die Leute gruselige Wörter wie Kovarianz und Kontravarianz. – Greg

+0

@Greg: Auf der positiven Seite, ich habe das Gefühl, dass mein * eigenes * Verständnis dieser Konzepte in letzter Zeit aufgrund der Fülle von Fragen wie dieser, die aufgetaucht sind, wirklich konkretisiert wurde! –

+0

Die Konzepte sind gut, aber die Namen sind gruselig. :) – Greg

Antwort

4

Hinweis: In allen Fällen werden Sie das Contents Feld auf ein konkretes Objekt initialisieren müssen, die IList<?>

implementiert Wenn Sie die generische Einschränkung halten, können Sie tun:

public IList<T> Contents = new List<T>(); 

Wenn Sie don ‚t, können Sie tun:

public IList<MyInterface> Contents = new List<MyInterface>(); 

Methode 1:

Ändern der Methode:

public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface 
{ 
    foreach (T myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

und die Schnipsel zu:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

Methode 2:

Alternativ bewegen, um die CallAllMethodsInContainer Verfahren zum MyContainer<T> Klasse wie folgt:

public void CallAllMyMethodsInContents() 
    { 
     foreach (T myClass in Contents) 
     { 
      myClass.MyMethod(); 
     } 
    } 

und das Snippet ändern:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
container.CallAllMyMethodsInContents(); 

Methode 3:

EDIT: Eine weitere Alternative ist die generische Einschränkung der MyContainer Klasse wie folgt zu entfernen:

public class MyContainer 
{ 
    public IList<MyInterface> Contents; 
} 

und um die Methodensignatur zu

zu ändern

Dann sollte der Schnipsel arbeiten, wie:

MyContainer container = new MyContainer(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

Beachten Sie, dass mit dieser Alternative die Contents Liste der Behälter wird jede Kombination von Objekten akzeptieren, die MyInterface implementieren.

3

Wow, diese Frage in letzter Zeit viel kommt worden ist.

Kurze Antwort: Nein, das ist nicht möglich.Hier ist, was ist möglich:

public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface 
{ 
    foreach (IMyInterface myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

Und hier ist der Grund, was Sie versucht ist nicht möglich (aus this recent answer of mine):

Betrachten Sie die List<T> Typ. Angenommen, Sie haben eine List<string> und eine List<object>. String leitet von Objekt ab, aber es folgt nicht, dass List<string> von List<object> abgeleitet wird; wenn es so wäre, dann könnte man Code wie diese:

var strings = new List<string>(); 

// If this cast were possible... 
var objects = (List<object>)strings; 

// ...crap! then you could add a DateTime to a List<string>! 
objects.Add(new DateTime(2010, 8, 23));23)); 

Der obige Code zeigt, was es zu sein bedeutet (und nicht zu sein) ein covariant type. Beachten Sie, dass das Gießen eines Typs T<D> zu einem anderen Typ T<B>, wobei von B abgeleitet wird, möglich ist (in .NET 4.0), wenn Tkovariant ist; Ein generischer Typ ist kovariant, wenn sein generisches Typargument immer nur in Form einer Ausgabe erscheint - d. h. schreibgeschützte Eigenschaften und Funktionsrückgabewerte.

Betrachten Sie es auf diese Weise: wenn irgendeine Art T<B> immer ein B liefert, dann eine, die immer liefert eine D (T<D>) in der Lage sein wird, als T<B> zu arbeiten, da alle D s sind B s.

Übrigens ist ein Typ contravariant, wenn sein generischer Typparameter immer nur in der Form von Eingabe erscheint - d. H. Methodenparameter. Wenn ein Typ T<B> kontravariant ist, kann er in eine T<D> umgewandelt werden, so seltsam das auch erscheinen mag.

Betrachten Sie es auf diese Weise: wenn irgendeine Art T<B> immer eine B erfordert, dann kann es in für einen Schritt, der immer eine D erfordert, da wiederum alle D s sind B s.

Ihre MyContainer Klasse ist weder covariant noch kontra, weil seine Art Parameter in beiden Kontexten erscheint - als Eingang (über Contents.Add) und als Ausgang (über die Contents Eigenschaft selbst).

Verwandte Themen