Frage zu Delegaten und Invokes

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

    Frage zu Delegaten und Invokes

    Ist das hier

    VB.NET-Quellcode

    1. Me.Invoke(Sub() clearButtons())


    genau das selbe wie Das

    VB.NET-Quellcode

    1. Private Delegate sub test()

    VB.NET-Quellcode

    1. Private sub macheDas()
    2. Dim test as new Test() AdressOf delegateAusführen()
    3. Me.invoke(test)
    4. end sub
    5. Private sub delegateAusführen()
    6. End sub


    Wenn nein Wo ligt der unterschied :P 8o
    Meine Programme
    TrackBoard 4 Music-Player Software
    Download:
    Klicke hier!

    Naja du hast mit deiner Aussage meine Frage eig schon beantwortet ich wollte halt nur den Unterschied von denen wissen den unterschied in das was die beiden Snippets tun bzw ob die Überhaubt das gleiche tun
    Meine Programme
    TrackBoard 4 Music-Player Software
    Download:
    Klicke hier!

    Nein ist klar das er es nicht Komiled die Snippets gehören ja auch noch zu nem anderen code und werden anders deklariert das sind nur die einzlnen teile um die es mir geht, Süßer!
    Meine Programme
    TrackBoard 4 Music-Player Software
    Download:
    Klicke hier!

    Da das schlecht in meine Erklärung unten passt halte ich das direkt hier oben fest:
    Das:

    VB.NET-Quellcode

    1. Me.Invoke(Foo)

    und das:

    VB.NET-Quellcode

    1. Dim Bar = Foo
    2. Me.Invoke(Bar)

    macht in Deinen beiden Beispielen keinen Unterschied. Es gibt Unterschiede zwischen Deinen beiden Beispielen, aber die haben nichts damit zu tun, ob man was erst einer lokalen Variable zuweist. Deshalb vereinfache ich diesen Teil in allen Codeschnipseln.
    Also:


    Hier vermischen sich mehrere Konzepte ein bisschen. Lass mich also genauer erklären:

    1.
    Einen Delegattyp deklarieren funktioniert so: Delegate Sub T(...) bzw. Delegate Function T(...) As ....
    Das deklariert den nicht-abstrakten Delegattyp T. Jeder nicht-abstrakte Delegattyp spezifiziert eine Signatur. Das heißt, eine Instanz eines solchen Delegattypen zeigt auf eine Methode, die eben jene Signatur hat oder damit kompatibel ist. (Kompatibel bedeutet, dass Vererbung beachtet wird, also z.B. eine Instanz eines Delegattypen, der eine Katze als Parameter entgegennehmen muss, auch auf eine Methode zeigen darf, die alle Arten von Tieren entgegennimmt.)

    2.
    Delegaten instanziieren funktioniert so: New T(M).
    T ist ein Delegattyp. Wichtig ist hier, dass es sich um einen nicht-abstrakten Delegattyp handelt. Ist wie bei ganz normalen Klassen: Katze ist ein ("erbt von") Tier. Du kannst eine Katze erstellen, aber kein Tier, weil Tier was abstraktes ist.
    M ist ein Methodenausdruck. Hier gibt es diese zwei Möglichkeiten:
    1. AddressOf N, wobei N der Name einer anderswo deklarierten Methode ist, welche eine mit dem Delegaten kompatible Signatur haben muss.
    2. Ein Lambdaausdruck, wobei der Lambdaausdruck eine mit dem Delegaten kompatible Signatur haben muss oder VB etwas "nachhelfen" können muss. Also z.B. wenn der Delegattyp Parameter spezifiziert, der Lambdaausdruck aber keine Parameter entgegennimmt, dann bekommt der Lambdaausdruck im Hintergrund trotzdem Parameter, die einfach "unsichtbar" sind.

    Beachte: In vielen Fällen ist es erlaubt, nur M als Ausdruck hinzuschreiben und der Compiler baut im Hintergrund die Instanziierung drumherum selbst zusammen. Wenn das Ziel für den Ausdruck ein nicht-abstrakter Delegattyp ist, dann wird als T eben jener Typ verwendet. Wenn der Typ ein abstrakter Delegattyp (z.B. beim Aufruf von Me.Invoke) oder nicht bekannt ist (z.B. bei Dim x = D mit Option Infer On), dann wird im Hintergrund ein anonymer Delegattyp deklariert und verwendet.

    3.
    Die Methode aufrufen, auf die eine Instanz eines Delegattyps zeigt, funktioniert so: D.Invoke(..), oder abgekürzt D(...), was aber auch nur zu vorherigem kompiliert.
    D ist irgend ein Ausdruck, der eine Instanz eines Delegattyps ergibt. Also zum Beispiel eine lokale Variable (wie in deinem zweiten Codeschnipsel) (aber jetzt nicht mit Me.Invoke verwechseln, welches damit nichts zu tun hat!), ein Parameter, ein Feld oder - wenn auch etwas unsinnig - eine direkte Instanziierung wie in Schritt 2.

    4.
    Oben erwähne ich immer wieder "nicht-abstrakte Delegattypen".
    Es gibt genau zwei abstrakte Delegattypen: System.Delegate und System.MulticastDelegate (erbt von System.Delegate). Diese beiden Delegattypen sind vom .NET-Framework vorgegeben. Sie besitzen keine eigene Signatur und deshalb auch keine Invoke-Methode, wie in Schritt 3 gezeigt und man kann deshalb auch keine Instanz davon erstellen. Ist wie gesagt genau wie mit Katzen und Tieren.
    Wie ruft Me.Invoke, welches ja einen System.Delegate entgegennimmt, dann die Methode auf, wenn es keine Invoke-Methode gibt? Nun, es gibt da noch die DynamicInvoke-Methode. Die nimmt ein Object-Array entgegen und macht im Grunde genommen das: D.Invoke(Args(0), Args(1), Args(2), ...). Also das erste Objekt im Array wird als erster Parameter verwendet, das zweite Element als zweiter Parameter, und so weiter. Das ist natürlich Fehleranfällig, weil der Compiler nicht wissen kann, ob die Anzahl an Parametern und deren Typen stimmen. Das wird erst zur Laufzeit geprüft und löst im Fehlerfall eine ArgumentException aus.
    Also das sind die abstrakten Delegattypen.
    Alle nicht-abstrakten Delegattypen erben von einem dieser beiden abstrakten Delegattypen. Genau genommen ist die Unterscheidung zwischen Delegate und MulticastDelegate unnütz und wurde nur behalten, weil es vor Release von .NET Framework 1.0 zu aufwändig gewesen wäre, die beiden Typen zusammenzufassen: blogs.msdn.microsoft.com/brada…te-and-multicastdelegate/ und genau genommen erben alle nicht-abstrakten Delegattypen einfach von System.MulticastDelegate.
    Jeder Delegattyp, den Du wie in Schritt 1 deklarierst, ist ein nicht-abstrakter Delegattyp, also mit Signatur, Invoke-Methode, und so weiter.
    Sinn der abstrakten Delegattypen ist einfach, dass man einen gemeinsamen Typ hat, mit dem man arbeiten kann. Wie eben die Me.Invoke-Methode.


    Mit diesem Wissen ausgestattet kann man jetzt Deine Codeschnipsel zerpflücken. Ich habe sie allerdings der Verständlichkeit wegen etwas aufgeräumt.
    Der einfachste Weg, herauszufinden, was im Hintergrund passiert, ist, den Code in einem Testprojekt zu kompilieren und anschließend mit z.B. ILSpy zu dekompilieren. Da kommt dann das heraus:
    Schnipsel 1

    VB.NET-Quellcode

    1. Public Class Test1
    2. Public Sub Invoke(Method As System.Delegate) 'Da wir uns nicht in einer Form befinden, schreibe ich hier eine äquivalente Invoke-Methode hin.
    3. End Sub
    4. Public Sub DoIt()
    5. Me.Invoke(Sub() ClearButtons())
    6. End Sub
    7. Public Sub ClearButtons()
    8. End Sub
    9. End Class

    Ergibt:

    VB.NET-Quellcode

    1. Friend Delegate Sub VB$AnonymousDelegate_0()
    2. Public Class Test1
    3. Public Sub Invoke(Method As System.Delegate)
    4. Sub
    5. Public Sub DoIt()
    6. Me.Invoke(New VB$AnonymousDelegate_0(AddressOf Me._Lambda$__1))
    7. End Sub
    8. Public Sub ClearButtons()
    9. End Sub
    10. Private Sub _Lambda$__1()
    11. Me.ClearButtons()
    12. End Sub
    13. End Class



    Schnipsel 2

    VB.NET-Quellcode

    1. Public Class Test2
    2. Public Sub Invoke(Method As System.Delegate)
    3. End Sub
    4. Public Sub DoIt()
    5. Me.Invoke(New TestDelegate(AddressOf ClearButtons))
    6. End Sub
    7. Public Delegate Sub TestDelegate()
    8. Public Sub ClearButtons()
    9. End Sub
    10. End Class

    Ergibt:

    VB.NET-Quellcode

    1. Public Class Test2
    2. Public Sub Invoke(Method As System.Delegate)
    3. End Sub
    4. Public Sub DoIt()
    5. Me.Invoke(New TestDelegate(AddressOf ClearButtons))
    6. End Sub
    7. Public Delegate Sub TestDelegate()
    8. Public Sub ClearButtons()
    9. End Sub
    10. End Class



    Also Schnipsel 2 ist die elementarste Form, die man haben kann. Du deklarierst einen Delegattyp, erstellst davon eine Instanz die auf die ClearButtons-Methode zeigt und schmeißt die Instanz in die Me.Invoke-Methode. Keine Magie, alles ganz einfach.

    Bei Schnipsel 1 hast Du einen Lambdaausdruck. Der wird zu einer anonymen Methode kompiliert. Der Inhalt dieser anonymen Methode ist einfach ein Aufruf an die ClearButtons-Methode. Da Du die Instanziierung nicht explizit hingeschrieben hast und die Me.Invoke-Methode einen abstrakten Delegattyp entgegennimmt, es also keinen nicht-abstrakten Delegattyp gibt, der verwendet werden könnte, wird ein anonymer Delegattyp deklariert und an der Stelle instanziiert. Ab jetzt gibt es nur noch ein paar kleine Details, in denen sich der Code von Schnipsel 2 unterscheidet. Also z.B. dass der anonyme Delegattyp in Schnipsel 1 ohne Namespace in der Assembly deklariert wird, aber so genau muss man es dann auch nicht wissen.



    Ein kleiner Nachtrag zu Schritt 2: Wenn M eine Instanzmethode ist, dann "merkt" sich die Instanz des Delegattyp an welcher Instanz die Methode, auf die gezeigt wird, aufgerufen werden muss.

    VB.NET-Quellcode

    1. Class Test
    2. Public Sub DoIt()
    3. End Sub
    4. End Class
    5. 'Dann:
    6. Dim A As New Test
    7. Dim B As New Test
    8. Dim DA As New Action(AddressOf A.DoIt)
    9. Dim DB As New Action(AddressOf B.DoIt)

    In diesem Beispiel zeigen DA und DB zwar auf die gleiche Methode aber "merken" sich unterschiedliche Instanzen.
    Wenn Du also Me.Invoke(New TestDelegate(AddressOf ClearButtons)) hinschreibst, dann steht da implizit eigentlich Me.Invoke(New TestDelegate(AddressOf Me.ClearButtons)). Da es sich also um eine Instanzmethode handelt, merkt sich die Instanz des Delegattyps hier ebenfalls, was Me ist.


    Und noch ein Nachtrag zur Nomenklatur:
    Ich habe mich bemüht, um Missverständnisse zu vermeiden, hier immer schön säuberlich "Instanz eines Delegattyps" hinzuschreiben. Gebräuchlich ist es aber, Instanzen von Delegattypen einfach nur "Delegaten" zu nennen. Also z.B. "Du erstellst einen Delegat, der auf die ClearButtons-Methode zeigt.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Niko Ortner schrieb:

    (Kompatibel bedeutet, dass Vererbung beachtet wird, also z.B. eine Instanz eines Delegattypen, der eine Katze als Parameter entgegennehmen muss, auch auf eine Methode zeigen darf, die alle Arten von Tieren entgegennimmt.)


    Umgekehrt, oder nicht? Der Delegattyp erwartet einen String, dann kann die Instanz des Delegattypen nicht auf eine Methode zeigen, die nur Objects akzeptiert. In deinem Beispiel: Der Delegattyp erwartet "Tier", dann darf ich natürlich auf eine Methode verweisen, die nur Katzen akzeptiert.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.

    Niko Ortner schrieb:

    Es gibt genau zwei abstrakte Delegattypen: System.Delegate und System.MulticastDelegate (erbt von System.Delegate).
    Inne Praxis gibts glaub nur einen, den MultiCast.
    Ich hab jdfs. noch nie einen Delegaten am Wickel gehabt, der nicht Multicast gewesen wäre, und ich hab auch kein Plan, wie ich einen Delegaten erstellen sollte, der nicht Multicast ist.
    Aber vlt. wisst ihr da ja was...
    @ErfinderDesRades Er schrieb ja ein paar Absätze weiter auch:

    Niko Ortner schrieb:

    Genau genommen ist die Unterscheidung zwischen Delegate und MulticastDelegate unnütz und wurde nur behalten, weil es vor Release von .NET Framework 1.0 zu aufwändig gewesen wäre, die beiden Typen zusammenzufassen: blogs.msdn.microsoft.com/brada…te-and-multicastdelegate/ und genau genommen erben alle nicht-abstrakten Delegattypen einfach von System.MulticastDelegate.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.

    Arby schrieb:

    Umgekehrt, oder nicht?

    Nein, schon so rum.
    Stell es Dir so vor:

    VB.NET-Quellcode

    1. Delegate Sub CatMethod(It As Cat)
    2. Sub DoIt()
    3. Dim MyCat As New Cat
    4. Dim FeedIt As New CatMethod(AddressOf FeedAnimal)
    5. FeedIt.Invoke(MyCat)
    6. End Sub
    7. Sub FeedAnimal(ToFeed As Animal)
    8. End Sub

    Die FeedAnimal-Methode darf Katzen und andere Tiere entgegennehmen. In die Invoke-Methode wird eine Katze reingeschmissen, der Delegattyp spezifiziert ja schließlich, dass eine Katze entgegengenommen wird. Klappt also.


    Aber anders rum wäre es falsch:

    VB.NET-Quellcode

    1. Delegate Sub AnimalMethod(It As Animal)
    2. Sub DoIt()
    3. Dim MyDog As New Dog
    4. Dim FeedIt As New AnimalMethod(AddressOf FeedCat)
    5. FeedIt.Invoke(MyDog)
    6. End Sub
    7. Sub FeedCat(ToFeed As Cat)
    8. End Sub

    Die FeedCat-Methode darf nur Katzen entgegennehmen. In die Invoke-Methode wird ein Hund reingeschmissen, der Delegattyp spezifiziert ja schließlich, dass alle Arten von Tieren entgegengenommen werden. Die FeedCat-Methode bekommt jetzt einen Hund, obwohl sie nur Katzen entgegennehmen kann. Das ist also falsch.

    Nach dem gleichen Prinzip (hier aber optisch andersrum) werden Rückgabetypen behandelt. Also eine Instanz eines Delegattyps, dessen Signatur ein Tier zurückgibt, darf auf eine Methode zeigen, die eine Katze zurückgibt.

    Eine nette Sache auch: Wenn die Signatur eines Delegattyps keinen Wert zurückgibt, darf man bei der Instanziierung auch einen Methodenausdruck verwenden, dessen Signatur einen Wert zurückgibt:

    VB.NET-Quellcode

    1. Delegate Sub Foo()
    2. Sub DoIt()
    3. Dim f As New Foo(AddressOf GetThing)
    4. f.Invoke()
    5. End Sub
    6. Function GetThing() As String
    7. Return "A"
    8. End Function

    Das wird von .NET nicht unterstützt. Hier baut der VB-Compiler eine anonyme Lambdamethode ein, die GetThing aufruft und den zurückgegebenen Wert verwirft (und selbst nichts zurückgibt), und lässt den Delegat darauf zeigen (statt direkt auf die GetThing-Methode).

    In C# ist das nicht erlaubt.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils