2010-02-26 11 views
7

Warum funktioniert das nicht? Versteh ich die Delegatenkovarianz nicht richtig?Delegieren Sie Kovarianz Verwirrung Conundrum!

public delegate void MyDelegate(object obj) 

public class MyClass 
{ 
    public MyClass() 
    { 
     //Error: Expected method with 'void MyDelegate(object)' signature 
     _delegate = MyMethod; 
    } 

    private MyDelegate _delegate; 

    public void MyMethod(SomeObject obj) 
    {} 

} 
+4

Ausreichende Alliteration immer amüsiert – Bob

+0

Ich habe versucht, solche Fragen in einem kurzen FAQ-Beitrag zu beantworten: http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx Es gibt immer noch eine Menge Verwirrung um dieses Feature ... –

Antwort

12

Richtig - Sie verstehen nicht Kovarianz richtig - noch :) Ihr Code würde funktionieren, wenn Sie die gleichen Typen hatten aber als Rückkehr Werte, wie folgt aus:

public delegate object MyDelegate() 

public class MyClass 
{ 
    public MyClass() 
    { 
     _delegate = MyMethod; 
    } 

    private MyDelegate _delegate; 

    public SomeObject MyMethod() { return null; } 
} 

Das Kovarianz zeigen würde . Alternativ können Sie es als Parameter halten, aber die Typen schalten um:

public delegate void MyDelegate(SomeObject obj) 

public class MyClass 
{ 
    public MyClass() 
    { 
     _delegate = MyMethod; 
    } 

    private MyDelegate _delegate; 

    public void MyMethod(object obj) {} 
} 

Das jetzt Kontra demonstriert.

Meine Faustregel ist, mich zu fragen, "gegeben der Delegat, was könnte ich damit machen? Wenn ich ein Argument übergeben kann, die die Methode brechen würde, sollte die Konvertierung fehlgeschlagen sein. Wenn die Methode etwas zurückgeben kann das würde den Aufrufer brechen, sollte die Konvertierung fehlgeschlagen sein. "

In Ihrem Code können Sie genannt haben:

_delegate(new object()); 

An diesem Punkt schlechte MyMethod einen Parameter hat, die gemeint ist als Typ SomeObject, ist aber tatsächlich vom Typ object. Dies wäre ein sehr schlechtes Ding, so dass der Compiler das verhindert.

Macht das alles mehr Sinn?

+0

Dies löst nicht das Problem des ursprünglichen Codes, wo ein generischer Delegat auf eine spezifischere Art und Weise implementiert werden muss. Das ist der springende Punkt von Generics in der Sprache. –

+0

Ich hatte es rückwärts und es macht jetzt viel mehr Sinn! Der allgemeinere Typ muss ein Parameter für die Methode sein, die nicht im Delegaten enthalten ist. Vielen Dank! –

+0

@Payton Byrd: Was lässt Sie denken, dass das Problem des ursprünglichen Codes ist? Ich ging davon aus, dass der OP wissen wollte, warum sein Code nicht funktionierte, da er diese Frage gestellt hatte. –

1

Der MyDelegate Typ erklärt, dass Sie in jede Art von Objekt passieren können. Allerdings MyMethod nimmt nur Objekte vom Typ SomeObject. Was passiert, wenn ich versuche, den Delegaten aufzurufen, der eine andere Art von Objekt übergibt: _delegate("a string object")? Gemäß der Deklaration von MyDelegate sollte dies zulässig sein, aber Ihre Funktion MyMethod kann tatsächlich kein Zeichenfolgenargument erhalten.

4

Sie müssen ein generisches verwenden.

EDIT: Warum? Da als weiteres Poster vermerkt ist, stimmen Object und SomeObject nicht mit dem Objekt überein, das möglicherweise nicht SomeObject ist. Das ist der ganze Punkt von Generics in der Sprache.

public delegate void MyDelegate<T>(T obj) 

public class MyClass 
{ 
    public MyClass() 
    { 
     _delegate = MyMethod; 
    } 

    private MyDelegate<SomeObject> _delegate; 

    public void MyMethod(SomeObject obj) 
    { 
    } 
} 
+0

Sie haben Recht, das war meine ursprüngliche Absicht mit dem Code! Ich schaute in, was 4.0 Angebote und sie erlauben einige ziemlich coole allgemeine Delegierte Sachen: http://blog.tlk.com/dot-net/2009/c-sharp-4-covariance-and-contravariance –

+1

@Adam: Sie Beachten Sie, dass die Abweichung, die Sie in Ihrem Beitrag untersucht haben, für C# 4.0 nicht neu ist - sie ist in C# 2.0 verfügbar. Es ist nur * generische * Varianz, die in C# 4 neu ist. –

4

Argumente kontra sind, Rückgabetypen covariant sind. Wenn der Delegat mit einer object aufgerufen wird, die keine Instanz von SomeObject ist, hätten Sie einen Tippfehler. Auf der anderen Seite ist die Rückgabe von SomeObject aus einer Routine, die in einen Delegaten eingepackt ist, der object zurückgibt, in Ordnung.

1

Vom MSDN Link zur Verfügung gestellt Du

Kovarianz ein Verfahren erlaubt einen mehr abgeleiteten Rückgabetyp als haben, was ist in den Delegaten definiert. Die Kontravarianz erlaubt eine Methode mit Parametertypen, die weniger abgeleitet sind als im Delegattyp.

Sie versuchen, eine abgeleitete Parametertyp verwenden die nicht unterstützt wird (obwohl .NET 4.0 wahrscheinlich, da dies hat viele Kovarianz/Kontra Probleme aussortiert).

+0

12. April rechts? Bald, bald, bald ... –

+1

Nein, dieser Code funktioniert auch nicht in C# 4.0. Andernfalls könnten Sie "object" an eine Methode übergeben, die "SomeObject" benötigt, was nicht sicher ist. –

0

Bei Kovarianz und Kontravarianz geht es darum, das Is-a-Prinzip der Vererbung zu verstehen.

In beiden, Kovarianz und Kontravarianz, s.th. wird entweder als Rückgabewert oder als Argument für die Delegate-Methode übergeben. Was "weitergegeben" wird, muss in einem Gefäß "gefangen" werden. In C# - oder Programmierjargon als solche - verwenden wir das Wort Eimer für das, was ich Gefäß genannt habe. Manchmal muss man auf andere Wörter zurückgreifen, um die Bedeutung gebräuchlicher Jargonwörter zu verstehen.

Wie auch immer, wenn Sie Vererbung verstehen, die wahrscheinlich jeder Leser hier wird, dann ist die einzige Sache, darauf zu achten, dass das Gefäß, ich. e. Der für den Fang verwendete Bucket muss vom gleichen Typ oder weniger abgeleiteten Typ sein als der, der übergeben wird - dies gilt sowohl für die Kovarianz als auch für die Kontravarianz.

Vererbung sagt, dass Sie einen Vogel in einem Tiereimer fangen können, weil der Vogel ein Tier ist. Wenn also ein Parameter einer Methode einen Vogel fangen muss, könnte man ihn in einem Tiereimer fangen (ein Parameter vom Typ Tier), was dann eine Kontravarianz ist. Und wenn Ihre Methode, dh Ihr Delegat einen Vogel zurückgibt, dann kann der "Eimer" auch vom Typ Vogel oder weniger abgeleitet (von einem Elterntyp) sein, was bedeutet, dass die Variable, von der Sie den Rückgabewert der Methode abfangen müssen der gleiche oder weniger abgeleitete Typ als der Rückgabewert.

Wechseln Sie einfach Ihr Denken, um zu unterscheiden zwischen dem, was passiert ist und dem, was gefangen wird, da dann alle Komplexität über Kovarianz und Kontravarianz sich gut auflöst. Dann erkennen Sie, dass das gleiche Prinzip am Werk ist. Es ist nur so, dass die Vererbung nicht verletzt werden kann, da sie nur in einer Richtung fließt. Der Compiler ist so intelligent, dass, wenn Sie den Bucket in den spezialisierteren Typ (wieder und wie nötig) umwandeln, dann und nur dann erhalten Sie alle spezialisierten Methoden zurück, die in die abgeleitete Klasse hinzugefügt wurden. Das ist die Schönheit davon. Also, es ist fangen, werfen und benutzen, was Sie haben und vielleicht brauchen.