Laden...

Funktionsweise von Methoden als Parameter in Methodenaufrufen [==> Delegaten]

Erstellt von joshit vor 8 Jahren Letzter Beitrag vor 8 Jahren 2.201 Views
J
joshit Themenstarter:in
34 Beiträge seit 2015
vor 8 Jahren
Funktionsweise von Methoden als Parameter in Methodenaufrufen [==> Delegaten]

Moin,

leider ist mir keine bessere Bezeichnung für mein Thema eingefallen... Wenn ihr da eine glücklichere Formulierung findet freue ich mich wenn Ihr die Bezeichnung ändert 😉

Nun aber ans eingemachte:

Ich sitze derzeit an einem Projekt, das mit RegEx bestimmte Werte aus einem Textfile holt, die Ergebnisse in ein anderes Format konvertiert und am Ende einen neuen File erstellt, der die konvertierten Ergebnisse enthält. Soweit zum "Bigger Picture".
Da ich mit Text arbeite, habe ich nach einer Lösung gesucht, mit der ich so lange wie möglich an meinem Datenmodell festhalten und erst im letzten Moment die Werte aus meinem Datenmodell zu Text konvertieren kann.
Vor dieser Konvertierung habe ich eine List<MyObject>. Folgende Lösung habe ich gefunden: Ich habe eine Extension Method von StringBuilder gebastelt die folgendermaßen aussieht:


public static StringBuilder AppendCollection<T>(this StringBuilder sb, IEnumerable<T> collection, Func<T, int, string> method, int counter)
{
        foreach (T item in collection)
        {
            sb.AppendLine(method(item, counter));
            counter++;
        }

        return sb;
}

Die Methode method(item, counter) holt die betreffenden Werte aus MyObject und gibt einen string zurück.

Hierzu eine direkte Frage: Warum muss ich hier das Interface IEnumerable angeben und kann z.B. nicht List<T> sagen?

Der Aufruf der Methode sieht dann so aus:


sb.AppendCollection(ListOfMyObjects, converter.ConvertMyObjectToString, 3);

Hier würde mich interessieren, warum die Parameterliste von AppendCollection einen Methodenaufruf ohne Parameter akzeptiert?

Das alles funktioniert wunderbar, ich verstehe nur leider nicht warum.
Ich sehe sehr wohl, dass das eine relativ elegante Lösung ist, weil ich zur Laufzeit im Prinzip nicht mit endlos vielen Strings hantieren muss und es so nur zwei Fehlerquellen gibt: Die Konverter-Methode und die ExtensionMethod.
Die Lösung die ich vorher hatte sah da schon deutlich hölzerner aus (eigene Klasse die 15-20 strings konkatiniert). Ich bin mit der Lösung also durchaus zufrieden - ich würde nur sehr gerne verstehen, was sich hinter diesem Trick verbirgt. Dann sehe ich sowas in Zukunft nämlich gleich und verliere nicht 2 Wochen mit irgendeiner schwer testbaren und schlecht lesbaren Zweitlösung 😃

Ich würde mich sehr freuen wenn der eine oder andere sich die Mühe macht, mir zu helfen diese Verständnislücke zu schließen. Ich habe selbstverständlich auch schon kreuz und quer gegoogelt aber die Referenzen helfen mir an dieser Stelle nicht wirklich.

Viele Grüße
Joshit

Da kam eine Stimme aus dem Off: "Lächele, es könnte schlimmer kommen!" Ich lächelte. Und es kam schlimmer...

C
40 Beiträge seit 2011
vor 8 Jahren

Hallo joshit,

Func ist ein generisches Delegat das eine Rückgabe liefert und 0-n eigene Argumente akzeptiert. Es lassen sich somit alle Methoden angeben die, in deinem Fall, string zurückliefern und zusätzlich int und T als Argumente erwarten.

Alternativ kann natürlich auch ein Lambda Ausdruck übergeben werden:

var names = new List<string> { "Hans", "ist", "ein", "toller", "Hecht" };
var sb = new StringBuilder();
sb.AppendCollection(names, (s, i) => names[i].ToString(), counter);

Neben Func gibt es noch Action und Predicate, die man kennen sollte.

Gruß Chris

6.911 Beiträge seit 2009
vor 8 Jahren

Hallo joshit,

Warum muss ich hier das Interface IEnumerable angeben und kann z.B. nicht List<T> sagen?

Aufgrund des foreach kann deine Methode alles "verarbeiten" was IEnumerable ist*, daher wäre es wohl eine Einschränkung wenn du nur List<T> zulassen würdest.
Zu diesem Punkt kannst du dir auch die Diskussion in IList<T> oder List<T> als Parameter- bzw. Rückgabetyp? durchlesen.

* ganz exakt ist bei foreach nicht IEnumerable nötig, sondern ein bestimmtes Muster

warum die Parameterliste von AppendCollection einen Methodenaufruf ohne Parameter akzeptiert?

In der Signatur der Methode hast du ein Func<...>, also einen Delegaten, verwendet.

Zitat von: Delegaten (C#-Programmierhandbuch)
Ein Delegat ist ein Typ, der Verweise auf Methoden mit einer bestimmten Parameterliste und dem Rückgabetyp darstellt. Wenn Sie einen Delegaten instanziieren, können Sie jeder Methode seine Instanz mit einer kompatiblen Signatur und dem Rückgabetyp zuordnen. Sie können die Methode über die Delegatinstanz aufrufen.

Delegaten werden verwendet, um Methoden als Argumente an anderen Methoden zu übergeben.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

J
joshit Themenstarter:in
34 Beiträge seit 2015
vor 8 Jahren

Erstmal danke für Eure Antworten!!

@Chris: Die grundlegende Definition kenne ich - ich kann damit nur noch nicht so viel anfangen. Auch von Action und Predicate habe ich schon gelesen. Mir fehlt in gewisser Weise noch so ein bisschen der Zugang.
Ich will einfach nicht begreifen was im Hintergrund passiert - dieses Wissen ist für mich aber von entscheidender Bedeutung, weil nur wenn ich etwas wirklich verstanden habe, kann ich mich auch darauf verlassen, dass mein Gehirn mir in der entscheidenden Situation den richtigen Hinweis liefert. Wenn ich etwas nicht verstanden habe, kann ich im Endeffekt so lange rumprobieren, bis es funktioniert (nichts anderes ist bei meinem Beispiel passiert) - dieses Wissen kann ich aber nur reproduzieren - mir gelingt der Transfer nicht. Das heißt ich werde in Zunkunft nur erkennen, dass eine derartige Lösung möglich ist, wenn die Ausgangslage ähnlich oder gleich ist, wie bei dem Problem das ich jetzt gelöst habe.
Bestes Beispiel für das Dilemma: In dem Artikel den du mir verlinkt hast, gibt es den Abschnitt Func vs Predicate<T>. Das Beispiel ist für mich nicht nachvollziehbar, weil sich durch die dort gewählte Implementierung erstmal nichts ändert - ob ich der Console nun MyFunc oder MyMethod übergebe... Verstehst du mein Problem? 😉

Ich muss gleich voraus schicken, dass ich mit Delegates und Events schon immer meine liebe Not hatte. Irgendwo gibt es in meinem Kopf eine Blockade, die leider ungewohnt hartnäckig ist...

@gfoidl:

ganz exakt ist bei foreach nicht IEnumerable nötig, sondern ein bestimmtes Muster

Das wäre in diesem Fall die Methode GetEnumerator, richtig? Da List IEnumerable implementiert, müsste sie doch auch über das entsprechende Muster verfügen, oder? Deshalb wird doch ein Interface implementiert..?
Aber das gehört dann ja wirklich mehr in das generelle Thema Interfaces (aus der verlinkten Diskussion) und hat nichts mit meinem Delegaten zu tun...

In der Signatur der Methode hast du ein Func<...>, also einen Delegaten, verwendet.

Das heißt also, das Func<...> mein Delegat ist.

Für mich sieht das, aufgrund der vielen Möglichkeiten die ich habe, nach einer stinknormalen Definition aus. Ich sage meiner Extension im Prinzip nur, dass sie eine Methode erwarten soll, die einen unbestimmten Typ und einen integer als Parameter erwartet und mir einen string zurück gibt.

Das was ich mir zu Delegaten merken konnte, war das ich, etwas einfach ausgedrückt, präventiv einen delegaten definiere, der eine bestimmte Fähigkeit aufweist, die ich eventuell zur Laufzeit brauchen könnte. Tritt dann das dazu passende Event ein, wird der Delegat aufgerufen und die definierte Fähigkeit kommt zum tragen.

Dieses (Halb-)wissen lässt sich aber leider so gar nicht mit meinem Beispiel in Verbindung bringen...

Da kam eine Stimme aus dem Off: "Lächele, es könnte schlimmer kommen!" Ich lächelte. Und es kam schlimmer...

D
96 Beiträge seit 2012
vor 8 Jahren

Ich glaube du verwechselst Events und Delegates. Delegates sind einfach gesagt nur Referenzen auf Methoden. Du kannst diese dann einfach wie jede andere Methode auch aufrufen. Dabei muss antürlich die Signatur der aufzurufenden Methode bekannt sein. In deinem Fall sagt

Func<T, int, string>

dass es sich um eine Methode handelt, die ein Objekt von Typ T und einen Int als Argument bekommt und einen String zurückliefert.

Events hingegen benutzen zwar im Hintergrund nichts anderes als Delegates, aber sie bieten noch einen Zugriffsschutz von außen. Außenstehende Klassen z.B. können sich nur am Event registrieren (es abonieren), aber nicht selber auslösen (das Delegate dahinter ausführen).

public class MyClass
{
    public event Action<String> MyEvent;
    public Action<String> MyAction;
}

public class MyOtherClass
{

     public static void Main()
     {
         Action<String> action = (x) => System.Console.PrintLine(x);
         MyClass c = new MyClass();
         c.MyEvent += action; // Event abonieren
         c.MyAction = action; // Delegate setzen
         c.MyEvent("Some String"); // Error, da du das Event nicht von außen aufrufen kannst
         c.MyAction("Some String"); // Funktioniert, da das Feld MyAction auf eine Methode zeigt und diese somit aufgerufen wird
     }
}


Prinzipiell passiert bei dem Event und bei dem Delegate dasselbe, aber u.A. können Events nur von der implementierenden Klasse ausgelöst werden.

6.911 Beiträge seit 2009
vor 8 Jahren

Hallo joshit,

Das wäre in diesem Fall die Methode GetEnumerator, richtig?

Das ist nur ein Teil davon.

Zitat von: Following the pattern - Fabulous Adventures In Coding - Site Home - MSDN Blogs
a public method called GetEnumerator, and that must return some type that has a public property getter called Current and a public method MoveNext that returns a bool.

Aber wie du selbst schreibst, gehört das nicht hier ins Thema.

Das heißt also, das Func<...> mein Delegat ist.

Ja.

Ich sage meiner Extension im Prinzip nur, dass sie eine Methode erwarten soll, die einen unbestimmten Typ und einen integer als Parameter erwartet und mir einen string zurück gibt. Func<T, int, string> steht stellvertretend für jede Methode, welche einen passenden Rückgabewert und passende Argument hat. Stell dir das grob als Platzhalter vor.
Passen würde also


public string Foo(T arg1, int arg2);
private string Bar(T a, int b);
...

Zur Laufzeit kann dann dieser Platzhalter gefüllt werden und durch den Aufruf des Delegaten wird die übergebene Methode ausgeführt.

Für mich sieht das, ..., nach einer stinknormalen Definition aus.

Recht viel anders ist es auch nicht - mach dir das im Kopft nicht komplizierter als es ist.
Wie sonst willst du eine Methode als Argument in einer anderen Methode übergeben?
Wie sonst willst du eine Methode aufrufen, wenn du zur Entwurfszeit noch nicht weißt welche es ist?
Durch Platzhalter od. in der C#-Sprache: durch Delegaten.
Solltest du Wissen in C++ haben, so kannst du die Delegaten auch als Funktionszeiger vorstellen - die Konzepte sind ähnlich.

Dieses (Halb-)wissen lässt sich aber leider so gar nicht mit meinem Beispiel in Verbindung bringen...

Warum nicht? Du hast es doch in deinen Worten richtig wiedergegeben. Das passende "event" ist nur nicht als Ereignis, sondern als

  
sb.AppendCollection(ListOfMyObjects, converter.ConvertMyObjectToString, 3);  
  

zu sehen. Dann ist es wieder schlüssig und passend.

Wird es dir klar?

Schau dir auch [FAQ] Eigenen Event definieren / Information zu Events (Ereignis/Ereignisse) an, wenn du mit "event" und Delegat und Verständnisproblem hast.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
40 Beiträge seit 2011
vor 8 Jahren

Ich muss gleich voraus schicken, dass ich mit Delegates und Events schon immer meine liebe Not

Der Antwort von gfoidl kann ich eigentlich nichts mehr zufügen.
Vielleicht hilft das folgende Mini Beispiel das einen Rechner darstellt der sein Ergebnis nicht direkt zurückgibt sondern durch ein Event verarbeitet wird.


    class Program
    {
        static void Main(string[] args)
        {
            var calc = new CalculatorWithFunc();
            calc.Calculated += (s, e) => Console.WriteLine(string.Format("Computation result: {0}!", e.Result));

            // multiply
            calc.Compute(delegate(double val1, double val2) { return val1 * val2; }, 100, 0.5);

            // add
            calc.Compute((val1, val2) => val1 + val2, 50, 0.5);

            // divide
            calc.Compute(Divide, 100, 2);

            // subtract
            calc.Compute(Subtract, 100, 50);

            Console.ReadKey();
        }

        public static double Divide(double val1, double val2)
        {
            return val1 / val2;
        }

        // Ab C# 6 (Visual Studio 2015)
        public static double Subtract(double val1, double val2) => val1 - val2;
    }

    public class CalculationEventArgs : EventArgs
    {
        public double Result { get; set; }
    }

    public class CalculatorWithFunc
    {
        public event EventHandler<CalculationEventArgs> Calculated;

        public void Compute(Func<double, double, double> operation, double d1, double d2)
        {
            if (Calculated != null)
            {
                var result = operation(d1, d2);
                Calculated(this, new CalculationEventArgs { Result = result });
            }
        }
    }

Gruß Chris

J
joshit Themenstarter:in
34 Beiträge seit 2015
vor 8 Jahren

Was gibt es schöneres, als morgens im Büro anzukommen und als erstes weitere Antworten auf die derzeit brennendste Frage zu bekommen!! VIELEN DANK EUCH! Ich glaube ich bin so nah dran wie nie zuvor 😃

@DerKleineTomy:

Ich glaube du verwechselst Events und Delegates. Delegates sind einfach gesagt nur Referenzen auf Methoden. Du kannst diese dann einfach wie jede andere Methode auch aufrufen. Dabei muss antürlich die Signatur der aufzurufenden Methode bekannt sein.

So einfach?? Das glaub ich jetzt nicht 😉 dann wäre das Verhältnis vom Delegate zur Methode vergleichbar mit dem Verhältnis vom Objekt zum Wertdatentyp? Wenn das so ist, hat dieser Satz ein erstes riesiges Fragezeichen eliminiert!

Events hingegen benutzen zwar im Hintergrund nichts anderes als Delegates, aber sie bieten noch einen Zugriffsschutz von außen. Außenstehende Klassen z.B. können sich nur am Event registrieren (es abonieren), aber nicht selber auslösen (das Delegate dahinter ausführen).

Mit Realweltvergleichen: Ich kann die SZ abonieren, aber nicht selbst drucken.. Richtig?

Und so wie ich dich verstanden habe, bietet die Nutzung dieser Technik eine zusätzliche Absicherung, ähnlich wie bei Properties mit einem backing field..? Ich manipuliere nicht das Feld selbst sondern muss den Setter meiner Property ansprechen...

@gfoidl: Guten Morgen 😃 Auch wenns nicht mehr hergehört, danke für die Hinweise "has a public property getter called Current and a public method MoveNext" ...

Meine ersten Schritte hab ich mit C gemacht. Zeiger etc sind mir also zumindest ein Begriff. Aber es ist ja auch mit Referenz- und Werttypen ähnlich - oder mit Konstanten und Variablen (bitte berichtigt mich wenn ich auf einem totalen Holzweg bin).

mach dir das im Kopft nicht komplizierter als es ist.

Du hast schon recht. Ist auch nicht das erste Mal das mir das auf diese Weise passiert.. aber stell dir meinen Weg dahin vor: Ich habe eine Ellenlange instabile Klassendefinition (deutlich über 100 Zeilen) die meine Aufgabe löst. Dann finde ich eine Lösung mit der sich die insgesamte Zeilenanzahl auf maximal 25 Zeilen reduziert. Das sieht erstmal nach Magie aus 😉 und dementsprechend sagt mir mein Kopf: Wenn das das Programmieren so "einfach" macht, muss es kompliziert sein^^

Wenn ich den Methodenaufruf als Event sehen kann, dann bin ich langsam sehr sehr nah dran!! Und super Link. Da werde ich noch mal ein wenig nachlesen.

@Chris360: Das Beispiel ist super!! Verstehe ich das richtig, wenn ich sage, dass der Delegat die Methode Compute ist, das Event ist der Moment wo die Berechnung abgeschlossen ist und das Event löst die Ausgabe auf der Konsole aus? Bitte sag ja 😉

Euch allen dreien nochmals VIELEN, VIELEN DANK!!!

EDIT: Eine Sache habe ich noch nicht verstanden. Wenn ich mit func die Signatur vorgebe - wie und warum funktioniert der Parameterlose Aufruf??

Da kam eine Stimme aus dem Off: "Lächele, es könnte schlimmer kommen!" Ich lächelte. Und es kam schlimmer...

W
955 Beiträge seit 2010
vor 8 Jahren

Mit Realweltvergleichen: Ich kann die SZ abonieren, aber nicht selbst drucken.. Richtig? Das wichtige hier ist dass Du andere nicht davon abhalten kannst ebenfalls zu abonnieren. => Zugriffsschutz.

C
40 Beiträge seit 2011
vor 8 Jahren

Verstehe ich das richtig, wenn ich sage, dass der Delegat die Methode Compute ist

Compute ist selbst kein Delegate. Es nimmt nur einen Delegaten als operation an und führt diesen aus, sollte es Abonnenten am Calculated-Event geben. Die Eventauslösung ruft dann jene Abonnenten iterativ auf, sprich ihre Handler-Methoden.

Gruß Chris

O
79 Beiträge seit 2011
vor 8 Jahren

EDIT: Eine Sache habe ich noch nicht verstanden. Wenn ich mit func die Signatur vorgebe - wie und warum funktioniert der Parameterlose Aufruf??

Ich bezweifle, das das ernsthaft parameterlos ist. Die Deklaration von converter.ConvertMyObjectToString ist mit sicherheit irgendwas in der Art

public string ConvertMyObjectToString(T, int VerySpecialNeededNumber)

Du meldest ja in der Parameterliste einen Func<T, int, string> an. Damit ist nur der Name der Methode gemeint, die letztlich ausgeführt werden soll. Die Parameter für diese auszuführende Methode kommen ja woanders her:

sb.AppendLine(method(item, counter));

item und counter sind die Parameter, die an ConvertToMyObject übergeben werden - ergo an den Func<T, int, string> (string ist der Rückgabewert, kein Parameter !). Die Params müssen natürlich nicht mitangegeben werden, nur der Methodenname ist von Belang - ob die angegebene Methode auch paßt, prüft der Compiler.

Ich hoffe, das war verständlich. Ich habe auch ne ganze Weile damit auf Kriegsfuß gestanden, bis ich mir vor Augen gehalten habe, wie das in Delphi/Object Pascal letztendlich aussehen würde. Da gibts sowas auch.

74 Beiträge seit 2014
vor 8 Jahren

Guten Morgen,

Dein Methodenaufruf:


sb.AppendCollection(ListOfMyObjects, converter.ConvertMyObjectToString, 3);

wird beim Kompilieren zu so etwas wie:


sb.AppendCollection<MyObject>(ListOfMyObjects, new Func<MyObject, int, string>(converter.ConvertMyObjectToString), 3);

Da gibt es also gar keinen "parameterlosen Methodenaufruf". Der C#-Compiler weiß nur, dass wenn irgendwo ein Delegate erwartet wird, und eine Methode angegeben ist, dass er dies in einen Delegate verpacken muss.

Grüße

Edit: Weil es eine Erweiterungsmethode ist, sieht es eher so aus:


Extensions.AppendCollection<MyObject>(sb, ListOfMyObjects, new Func<MyObject, int, string>(converter.ConvertMyObjectToString), 3);

J
joshit Themenstarter:in
34 Beiträge seit 2015
vor 8 Jahren

Langsam setzt sich das bei mir 😉

Das Rätsel mit dem Aufruf ist tatsächlich gelöst. Der Aufruf der Extension braucht, wie einige schon vermutet haben, nur den Namen der Methode. Die Methode selbst wird erst in der Extensionmethod aufgerufen. MyObject steht in meiner Liste die mit foreach durchlaufen wird. Der string, der zurückgegeben wird, wird dem AppendLine-Aufruf als Argument übergeben und mein int übergebe ich ja bereits der AppendCollection.

@OlafSt: Leider habe ich weder Kenntnisse in Delphi/Object Pascal, ich werde also andere Ansätze finden müssen 😉 aber ich habe hier auch schon großartige Hilfe bekommen! Einige Unklarheiten müssen noch beseitigt werden - aber das hört wohl nie auf 😁

@Chris360: Da haben mich meine Augen wohl im Stich gelassen. Macht wenig Sinn was ich gesagt habe.. Danke für die Berichtigung!

An alle anderen: Vielen Dank nochmal fürs lesen, nachdenken und Eure hilfreichen Beiträge!

Lieben Gruß,

joshit

Da kam eine Stimme aus dem Off: "Lächele, es könnte schlimmer kommen!" Ich lächelte. Und es kam schlimmer...