it-swarm.com.de

Warum sollte ich jemals Delegierte einsetzen, wenn ich keine Veranstaltungen mache?

Ich versuche zu lernen, wie man Ereignisse in C # ausführt, und gemäß dem MSDN-Ereignis-Tutorial ,

Ereignisse werden mit Delegaten deklariert. Wenn Sie das Delegierten-Lernprogramm noch nicht studiert haben, sollten Sie dies tun, bevor Sie fortfahren

jetzt versuche ich zuerst, die Delegierten zu verstehen und scheitere schrecklich. Wofür sind sie? Welches Problem versuchen Sie zu lösen, wo Sie etwas delegieren?

Ich denke, sie dienen dazu, Methoden an andere Methoden weiterzugeben, aber worum geht es dabei? Warum nicht einfach eine Methode haben, die alles tut, was Sie tun?

26
medivh

Das Übergeben von Funktionen an andere Funktionen ist eine sehr gute Möglichkeit, Code zu verallgemeinern , insbesondere wenn Sie viel nahezu doppelten Code haben, wobei sich nur die Logik in der Mitte unterscheidet .

Mein bevorzugtes (einfaches) Beispiel hierfür ist die Erstellung einer Benchmark-Funktion, die einen Delegaten akzeptiert, der den Code enthält, den Sie als Benchmark verwenden möchten. Ich werde Lambda-Syntax und Func/Action Objekte verwenden, da diese viel prägnanter (und häufiger) sind als heutzutage explizit erstellte Delegaten.

public void Benchmark(int times, Action func)
{
    var watch = new Stopwatch();
    double totalTime = 0.0;

    for (int i = 0; i < times; i++)
    {
        watch.Start();
        func(); // Execute our injected function
        watch.Stop();

        totalTime += watch.EllapsedTimeMilliseconds;
        watch.Reset();
    }

    double averageTime = totalTime / times;
    Console.WriteLine("{0}ms", averageTime);
}

Sie können jetzt einen beliebigen Codeblock an diese Benchmark-Funktion übergeben (zusammen mit der Häufigkeit, mit der Sie sie ausführen möchten) und die durchschnittliche Ausführungszeit zurückerhalten!

// Benchmark the amount of time it takes to ToList a range of 100,000 items
Benchmark(5, () =>
{
    var xs = Enumerable.Range(0, 100000).ToList();
});

// You can also pass in any void Function() by its handler
Benchmark(5, SomeExpensiveFunction);

Wenn Sie den Code, den Sie als Benchmark verwenden möchten, nicht in die Mitte dieser Funktion einfügen könnten, würden Sie die Logik wahrscheinlich überall kopieren und einfügen, wo Sie sie verwenden möchten.

Linq nutzt die Funktionsübergabe in großem Umfang, sodass Sie eine ganze Reihe wirklich flexibler Operationen an Datensätzen ausführen können. Nehmen wir als Beispiel die Funktion Where. Es filtert die Elemente einer Liste heraus, die "true" zurückgeben, wenn eine Vergleichsfunktion übergeben wird.

var xs = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// This function compares each element, and returns true for those that are above 5
//   Result = { 6, 7, 8, 9, 10 }
var above5 = xs.Where((x) => x > 5);

// This function only returns true if an element is even
//   Result = { 2, 4, 6, 8, 10 }
var evens = xs.Where((x) => x % 2 == 0);

Die Funktion Where selbst ist sehr verallgemeinert. Es durchläuft einfach eine Sammlung und liefert eine neue Sammlung, die nur die Werte enthält, die einer Prädikatfunktion entsprechen. Es liegt an Ihnen, den Code einzufügen, der genau sagt, wonach gesucht wird.

58
KChaloux

Angenommen, Sie müssen eine komplizierte mathematische Berechnung durchführen, bei der Rundung ausgeführt wird. Angenommen, Sie möchten manchmal, dass die Rundung rund auf gerade (auch als Banker-Rundung bezeichnet) ist, aber manchmal möchten Sie von Null weg runden (auch bekannt als 'Aufrunden [absolut] Wert]').

Jetzt können Sie zwei Methoden schreiben:

double DoTheCalculationWithRoundToEven(double input1, double input2)
{
    var intermediate1 = Math.Pow(input1, input2);
    // more calculations...
    var intermediate5 = Math.Round(intermediate4, MidpointRounding.ToEven);
    // more calculations...
    var intermediate10 = Math.Abs(intermediate9);

    return intermediate10;
}

double DoTheCalculationWithRoundAwayFromZero(double input1, double input2)
{
    var intermediate1 = Math.Pow(input1, input2);
    // more calculations...
    var intermediate5 = Math.Round(intermediate4, MidpointRounding.AwayFromZero);
    // more calculations...
    var intermediate10 = Math.Abs(intermediate9);

    return intermediate10;
}

wobei die beiden Methoden gleich sind außer für diese eine Zeile in der Mitte.

Nun, das funktioniert, ABER Sie haben doppelten Code und die sehr reale Möglichkeit, dass ein zukünftiger Betreuer (zum Beispiel ein zukünftiger Sie) eine Version des intermediate2 bis intermediate3 und vergiss den anderen.

Ein Delegattyp und ein Parameter können diese Duplizierung lösen.

Deklarieren Sie zuerst den Delegatentyp:

    public delegate double Rounder(double value);

Dies besagt, dass ein Rounder eine Methode ist, die ein double akzeptiert und ein double zurückgibt.

Als nächstes haben Sie eins Berechnungsmethode, die ein Rounder akzeptiert und es zum Runden verwendet:

double DoTheCalculation(double input1, double input2, Rounder rounder)
{
    var intermediate1 = Math.Pow(input1, input2);
    // more calculations...
    var intermediate5 = rounder(intermediate4);
    // more calculations...
    var intermediate10 = Math.Abs(intermediate9);

    return intermediate10;
}

Wenn Sie die Berechnung durchführen möchten, übergeben Sie schließlich ein entsprechendes Rounder. Sie können alles mit folgenden Methoden formulieren:

double RounderAwayFromZero(double value)
{
    return Math.Round(value, MidpointRounding.AwayFromZero);
}
// same for ToEven

rufen Sie dann eine der folgenden Optionen an:

var result = DoTheCalculation(input1, input2, RounderAwayFromZero);

oder

var result = DoTheCalculation(input1, input2, RounderToEven);

mit C # können Sie dies alles inline mit Lambdas tun:

var result = DoTheCalculation(input1, input2, 
     value => Math.Round(value, MidpointRounding.AwayFromZero));

dabei definiert der Lambda-Ausdruck eine Methode mit der richtigen Signatur als Rounder.

11
AakashM

Delegierte dienen dazu, eine Aktion zu parametrisieren. Der Funktion, die einem Delegaten übergeben wird, ist es egal, wie die Aktion ausgeführt wird, nur dass sie ausgeführt wird.

Schauen Sie sich zum Beispiel die Schnittstelle IComparer an. Sie können eine Liste sortieren, ohne sich darauf verlassen zu müssen, wie der Typ in der Liste "natürlich" sortiert ist. Wenn Sie sich jedoch die Schnittstelle ansehen, wird nur eine Methode definiert. Es ist ziemlich verschwenderisch, eine ganze Klasse zu definieren, die nur eine einzige Funktion implementiert.

Delegierte lösen dieses Problem1. Sie müssen keine Klasse definieren, sondern nur die Methode. Sie können den Boilerplate-Code entfernen und sich auf das konzentrieren, was Sie tun möchten. Lambdas machen einen Schritt nach vorne, damit die Funktionsdefinition eingefügt werden kann, anstatt irgendwo anders in der Klasse definiert zu werden.

1 Wenn Sie sich Sort() ansehen, existiert eine überladene Version, die einen Delegaten verwendet.

1
unholysampler

Delegaten ähneln Schnittstellen. Sie definieren einfach einen Vertrag, der von jedem erfüllt werden kann, der die Klasse verbraucht, die den Delegaten verwendet. Der große Unterschied zwischen einer Schnittstelle und einem Delegaten besteht darin, dass eine Schnittstelle einen Vertrag für eine gesamte Klasse definiert, während ein Delegat einen Vertrag für eine einzelne Methode definiert.

Das beste und grundlegendste Beispiel für die Verwendung von Delegierten in der realen Welt ist das Muster "Loch in der Mitte". Ich brauche dieses Muster nicht sehr oft, aber wenn ich es tue, ist es äußerst wertvoll. Sie können dasselbe Muster mithilfe von Vererbungs- und abstrakten Methoden ausführen. Mit Delegaten können Sie dies jedoch mithilfe der Komposition erreichen.

public delegate void MyDelegate(string param1, string param2);

public void SomeMethod(string param1, string param2) {
    Console.WriteLine(param1);
    //do some more work
}

public void DoSomething(MyDelegate somemethod) {
    //do something here
    somemethod(param1, param2);
    //do something else here
}
1
Mike Cellini

Der Zweck der Übergabe einer Methode an eine andere Methode besteht darin, die Zwecke dieser beiden Methoden zu trennen. Dies steht im Einklang mit den anerkannten Prinzipien des objektorientierten Entwurfs, insbesondere dem Prinzip der Einzelverantwortung: "Ein Codeobjekt sollte eine Sache tun und das einzige Codeobjekt in der Codebasis sein, das diese Sache tut.".

Nehmen wir an, Sie haben eine Methode, die eine Datei in einem bestimmten Format liest (z. B. durch Pipe getrennter Text), und eine Methode, die die Informationen in dieser Datei auf eine bestimmte Weise verarbeitet, um einige Informationen zu extrahieren. Sie sagen, Sie sollten diese in einer Methode zusammenstellen. Was passiert, wenn das Programm mit dem Lesen von CSV-Dateien beginnen muss? Was passiert, wenn Sie eine andere Teilmenge der darin enthaltenen Daten benötigen? Sie müssen die Methode ändern. Das Programm muss jedoch weiterhin durch Pipe getrennten Text verarbeiten und den älteren Datensatz abrufen. Jetzt haben Sie eine Methode, die aus zwei verschiedenen Dateitypen liest und zwei verschiedene Datensätze erzeugt. Diese Methode ist schnell auf dem Weg, nicht mehr zu warten.

Stattdessen können Sie eine Methode erstellen, die durch Pipe getrennten Text liest, und eine andere, die CSVs liest. Sie können eine Methode erstellen, die einen Datensatz extrahiert, und eine andere, die den zweiten extrahiert. Anschließend können Sie die Methode zum Lesen von Dateien als Delegat für die Datenextraktionsmethode angeben oder umgekehrt. Sie können sie auch einer dritten Methodenklasse zuweisen, die sie einfach der Reihe nach ausführt und die Daten aus der Datei an die Methode zum Extrahieren von Datensätzen weitergibt.

Auf diese Weise erhöhen Sie die Anzahl der Methoden, die Sie schreiben, aber jede Methode hat nur einen Zweck. Änderungen, die Sie vornehmen müssen, damit die Methode ordnungsgemäß funktioniert, wirken sich nicht auf den Zweck oder die Ausführung anderer Methoden aus. Der resultierende Code ist leichter zu verstehen, leichter zu warten und erfordert daher weniger Tests, um seine Richtigkeit sicherzustellen.

1
KeithS

Es gibt eine Vielzahl von Framework-Klassenbibliothekstypen mit Operationen, die Delegaten als Parameter verwenden.

Wenn Sie Task Parallel Library , LINQ oder Entity Framework verwenden, übergeben Sie Delegaten als Argumente.

Viele der Delegatensignaturen, die Sie verwenden möchten, sind bereits definiert ( Aktion , Func , Prädikat ) und die Syntax wurde mit Lambda-Ausdrücken vereinfacht .

0
brian

Der wahrscheinlich einfachste Weg, um Delegierte und Ereignisse zu verstehen, ist die GUI-Programmierung. Nehmen wir einen sehr häufigen Fall: Sie haben ein Formular mit einer Schaltfläche, und wenn der Benutzer auf die Schaltfläche klickt, soll ein bestimmter Code ausgeführt werden.

Der Typ, der die Button-Klasse geschrieben hat, hat offensichtlich keine Ahnung, was Sie damit machen wollen. Daher kann er Ihren Code für "Was diese Schaltfläche tun soll" nicht in seine Klasse einbauen. Und selbst wenn er könnte, sollte er es wirklich nicht, denn dann hört es auf, eine "GUI-Schaltfläche" zu sein und verwandelt sich in eine "Medivh-Schaltfläche für diesen speziellen Anwendungsfall", die im allgemeinen Fall viel weniger nützlich ist.

Stattdessen hat der Typ, der die Schaltflächenklasse geschrieben hat, ein Ereignis eingefügt, das eine Art Haken in die Funktionalität des Objekts darstellt. Ein Ereignis ist im Grunde ein Teil des Codes, der ausgeführt wird, in den jemand anderes Code einfügen kann. Wenn also jemand auf die Schaltfläche klickt, wird das OnClick-Ereignis ausgelöst. Wenn Sie diesem Ereignis etwas angehängt haben, z. B. eine Methode (einen Delegaten), die das Öffnen eines neuen Formulars bewirkt, wird dieser Delegat an dieser Stelle ausgeführt Punkt.

Wenn Sie Methoden nicht nur als eine Prozedur oder Funktion betrachten, die Sie aufrufen können, sondern als ein Objekt, das Sie an anderen Code übergeben können, eröffnen sich Ihnen alle möglichen neuen Möglichkeiten. Einige der anderen Personen hier haben bereits LINQ und die Task Parallel Library erwähnt, die Algorithmen enthalten, die die Grundidee von etwas definieren, und Sie dann einen Delegierten übergeben lassen, um die Details einzugeben.

0
Mason Wheeler