2013-03-25 7 views
8

ich einige Probleme, zu verstehen, warum die folgenden Ausschnitt geben Sie mir keinen Fehlergenerische Einschränkung für die Aktion funktioniert nicht wie erwartet

public void SomeMethod<T>(T arg) where T : MyInterface 
{ 
    MyInterface e = arg; 
} 

Aber dieses, was ich erwarten würde aufgrund der generischen Arbeit Typeinschränkung

private readonly IList<Action<MyInterface>> myActionList = new List<Action<MyInterface>>(); 

public IDisposable Subscribe<T>(Action<T> callback) where T: MyInterface 
{ 
    myActionList.Add(callback); // doesn't compile 
    return null 
} 

gibt diesen Fehler

cannot convert from 'System.Action<T>' to 'System.Action<MyInterface>' 

ich bin mit VS2012 SP1 und .NET 4.5.

Kann jemand erklären, warum die Beschränkung es nicht erlaubt zu kompilieren?

+1

Warum ist Ihre Liste readonly, und warum 'neue IList'? Ist das die eigentliche Erklärung? –

+1

Klassen und Delegierte sind nicht dasselbe. 'System.Action ' stellt eine Funktion mit einem einzelnen Parameter vom Typ 'MyInterface' dar, während 'System.Action ' eine Methode mit einem Parameter vom Typ 'T: MyInterface' darstellt. Die Funktionssignaturen sind nicht kompatibel, es ist nicht relevant, dass "T" eine Ableitung von "MyInterface" ist, die Signatur wäre nur kompatibel, wenn "T" genau "MyInterface" wäre. –

+0

@PaoloTedesco Entschuldigung, es ist rekonstruiert und vereinfacht von einem anderen Code. Kopieren/Einfügen Fehler –

Antwort

3

Klassen und Delegaten sind nicht das Gleiche. System.Action<MyInterface> repräsentiert eine Funktion mit einem einzelnen Parameter vom Typ MyInterface während System.Action<T> eine Methode mit einem Parameter vom Typ T : MyInterface darstellt. Die Funktionssignaturen sind nicht kompatibel, es ist nicht relevant, dass T eine Ableitung von MyInterface ist, die Signatur wäre nur kompatibel, wenn T genau MyInterface wäre.

0

Wenn T ohnehin auf eine bestimmte Schnittstelle beschränkt ist, können Sie nur diese Schnittstelle an ihrer Stelle verwendet werden:

public void SomeMethod(MyInterface arg) 
{ 
    MyInterface e = arg; 
} 

private readonly IList<Action<MyInterface>> myActionList = new IList<Action<MyInterface>>(); 

public IDisposable Subscribe(Action<MyInterface> callback) 
{ 
    myActionList.Add(callback); // does compile 
    return null 
} 

Wird arbeiten und kompilieren und ist praktisch die gleiche wie das, was Sie jetzt haben.

Generics sind nützlich, wenn Sie die gleiche Operation unabhängig vom Typ, wenn Sie dann den Typ auf eine Schnittstelle beschränken möchten, haben Sie den Zweck der Generika besiegt und sollte wahrscheinlich nur diese Schnittstelle stattdessen verwenden.

+0

Ich denke, er möchte in der Lage sein, Subtypen dieser spezifischen Schnittstelle zu übergeben. MyOtherInterface: MyInterface –

+0

@Roger, das könnte wahr sein, ich habe das nicht vom OP bekommen, aber ich gebe zu, es ist ein Ansatz, den ich auch nicht berücksichtigt habe. – Bazzz

4

Dies ist ein Kontra Ausgabe - ein Action<MyInterface> sollte jede MyInterface Instanz als Argument nehmen können, versuchen Sie jedoch ein Action<T> zu speichern, wo T einige Subtyp von MyInterface ist, was nicht sicher ist.

Zum Beispiel, wenn Sie hatte:

public class SomeImpl : MyInterface { } 
public class SomeOtherImpl : MyInterface { } 
List<Action<MyInterface>> list; 

list.Add(new Action<SomeImpl>(i => { })); 
ActionMyInterface act = list[0]; 
act(new SomeOtherImpl()); 

Sie nur eine Action<T> zu einem gewissen Action<U> zuweisen können, wenn der Typ T ist ‚kleiner‘ als der Typ U. Beispiel:

Action<string> act = new Action<object>(o => { }); 

ist sicher, da ein Zeichenfolgenargument immer gültig ist, wenn ein Objektargument vorhanden ist.

+0

+1 für Erklärung mit Beispiel. – nawfal

1

Die where T: MyInterface constraint bedeutet "jede Instanz von jede Klasse oder Struktur, die MyInterface implementiert".

Also, was Sie versuchen können zu tun, wie dies vereinfacht werden:

Action<IList> listAction = null; 
Action<IEnumerable> enumAction = listAction; 

die angeblich nicht arbeiten, während immer noch IList : IEnumerable.Weitere Details finden Sie hier:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx http://msdn.microsoft.com/en-us/library/dd799517.aspx

Also, wenn Sie wirklich generische und nicht nur Schnittstelle verwenden müssen - Sie können es so machen kann, wenn es Probleme Komplexität und kleinere Leistung ergänzt:

public static IDisposable Subscribe<T>(Action<T> callback) where T : MyInterface 
{ 
    myActionList.Add(t => callback((T)t)); // this compiles and work 
    return null; 
} 
1

Klassen und Delegierte verhalten sich ein wenig anders. Mal sieht, ein einfaches Beispiel:

public void SomeMethod<T>(T arg) where T : MyInterface 
{ 
    MyInterface e = arg; 
} 

In diesem Verfahren könnte man davon ausgehen, dass T zumindest MyInterface wäre, so dass Sie so etwas wie diese MyInterface e = arg; tun können, weil args immer MyInterface gegossen werden kann.

Nun wollen wir sehen, wie die Delegierten verhalten:

public class BaseClass { }; 
public class DerivedClass : BaseClass { }; 
private readonly IList<Action<BaseClass >> myActionList = new List<Action<BaseClass>>(); 

public void Subscribe<T>(Action<T> callback) where T: BaseClass 
{ 
    myActionList.Add(callback); // so you could add more 'derived' callback here Action<DerivedClass> 
    return null; 
} 

Jetzt Hinzufügen we'r DerivedClass Rückruf myActionList und dann irgendwo Sie rufen die Delegierten:

foreach(var action in myActionList) { 
    action(new BaseClass); 
} 

Aber man kann das nicht tun, weil Wenn Sie den Rückruf von DerivedClass haben, müssen Sie ihm DerivedClass als Parameter übergeben.

Diese Frage bezieht sich auf Covariance and contravariance. Sie könnten über die Abweichung von this Artikel lesen, auch Eric Lippert hat sehr interessante Artikel über die Varianz, this ist der erste Artikel, den Rest finden Sie in seinem Blog.

P.S. Bearbeitet nach Lee Kommentar.

+0

Klassen können nicht kovariant sein - nur Delegaten und Schnittstellen können Varianzanmerkungen haben.Es ist auch irreführend zu sagen, dass die Delegaten "Contravarianz" haben - die "Func" -Delegatentypen sind in ihren Argumenten kontravariant und in ihren Rückgabetypen kovariant. Kontravarianz ist nicht auf Delegattypen beschränkt, siehe zum Beispiel die Schnittstelle "IObserver ", die in "T" kontravariant ist. – Lee

+0

Ich habe meinen Beitrag bearbeitet, danke. – Andrew

2

Ich finde es hilfreich in diesen Situationen zu überlegen, was schief geht, wenn Sie das Verhalten zulassen. Lasst uns das bedenken.

interface IAnimal { void Eat(); } 
class Tiger : IAnimal 
{ 
    public void Eat() { ... } 
    public void Pounce() { ... } 
} 
class Giraffe : IAnimal 
... 
public void Subscribe<T>(Action<T> callback) where T: IAnimal 
{ 
    Action<IAnimal> myAction = callback; // doesn't compile but pretend it does. 
    myAction(new Giraffe()); // Obviously legal; Giraffe implements IAnimal 
} 
... 
Subscribe<Tiger>((Tiger t)=>{ t.Pounce(); }); 

Was passiert also? Wir erstellen einen Delegierten, der einen Tiger nimmt und aufgibt, ihn an Subscribe<Tiger> weitergibt, diesen in Action<IAnimal> umwandelt und eine Giraffe passiert, die dann aufspringt.

Offensichtlich muss das illegal sein. Der einzige Ort, an dem es sinnvoll ist, ihn rechtswidrig zu machen, ist die Umstellung von Action<Tiger> auf Action<IAnimal>. Das ist also illegal.