2010-08-25 15 views
9

Ho difficoltà a capire come funziona il polimorfismo quando si usano i farmaci generici. Per fare un esempio, ho definito il seguente programma:Per favore aiutami a capire il polimorfismo quando usi i farmaci generici 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; 
} 

posso poi fare questo, che funziona bene:

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

Ho molte classi che implementano MyInterface. Vorrei scrivere un metodo che può accettare tutti gli oggetti myContainer:

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

Ora, vorrei chiamare questo metodo.

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

Che non ha funzionato. Sicuramente, poiché MyClass implementa IMyInterface, dovrei essere in grado di lanciarlo semplicemente?

MyContainer<IMyInterface> newContainer = (MyContainer<IMyInterface>)container; 

Anche questo non ha funzionato. Posso sicuramente lanciare un normale MyClass a IMyInterface:

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

Così, almeno non ho del tutto capito male quello. Non sono sicuro di come scriverò un metodo che accetti una raccolta generica di classi conformi alla stessa interfaccia.

Ho un piano per risolvere completamente questo problema, se necessario, ma preferirei farlo correttamente.

Grazie in anticipo.

+2

Qui è dove la gente tira fuori parole spaventose come covarianza e controvarianza. – Greg

+0

@Greg: Per quanto mi riguarda, mi sento come se la mia * comprensione * di questi concetti si sia concretizzata di recente a causa della pletora di domande come questa che sono apparse! –

+0

I concetti sono buoni, ma i nomi sono spaventosi. :) – Greg

risposta

4

Nota: In tutti i casi, si dovrà inizializzare il campo Contents ad un oggetto concreto che implementa IList<?>

Quando si tiene il vincolo generico, è possibile fare:

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

Quando don 't, si può fare:

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

Metodo 1:

Modificare il metodo per:

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

e il frammento di:

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

Metodo 2:

alternativa, spostare il metodo CallAllMethodsInContainer alla classe MyContainer<T> simili:

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

e modificare il frammento di:

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

Metodo 3:

EDIT: Ancora un'altra alternativa è quella di rimuovere il vincolo generico dalla classe MyContainer simili:

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

e per modificare la firma del metodo su

public void CallAllMethodsInContainer(MyContainer container) 

Poi il frammento dovrebbe funzionare come:

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

Si noti che con questa alternativa, la lista del contenitore Contents accetterà qualsiasi combinazione di oggetti che implementano MyInterface.

3

Wow, questa domanda è stato arrivando molto ultimamente.

Risposta breve: No, questo non è possibile.Ecco cosa è possibile:

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

Ed ecco il motivo per cui ciò che si è tentato non è possibile (tratto da this recent answer of mine):

considerare il tipo List<T>. Supponiamo che tu abbia un List<string> e un List<object>. la stringa deriva dall'oggetto, ma non ne consegue che List<string> derivi da List<object>; se così fosse, allora si potrebbe avere codice come questo:

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)); 

Il codice di cui sopra illustra ciò che significa essere (e non essere) un covariant type. Si noti che il casting di un tipo T<B> in cui D0viene fornito da B (in .NET 4.0) se T è covariante; un tipo generico è covariante se il suo argomento di tipo generico appare sempre sotto forma di output - cioè, proprietà di sola lettura e valori restituiti dalla funzione.

Pensare in questo modo: se un certo tipo T<B> sempre fornisce una B, poi uno che fornisce sempre un D (T<D>) sarà in grado di operare come un T<B> dal momento che tutti D s sono B s.

Per inciso, un tipo è controvariante se il suo parametro di tipo generico appare sempre solo sotto forma di input, cioè i parametri del metodo. Se un tipo T<B> è controverso, può essere trasmesso a T<D>, per quanto strano possa sembrare.

Pensare in questo modo: se un certo tipo T<B> sempre richiede un B, allora può intervenire per quella che richiede sempre un D poiché, ancora una volta, tutti i D s sono B s.

La vostra classe MyContainer è né covariante né contravariant perché il suo parametro di tipo compare in entrambi i contesti - come input (via Contents.Add) ed in uscita (tramite la proprietà Contents stesso).