C# Language
Delegierte
Suche…
Bemerkungen
Zusammenfassung
Ein Delegattyp ist ein Typ, der eine bestimmte Methodensignatur darstellt. Eine Instanz dieses Typs bezieht sich auf eine bestimmte Methode mit einer übereinstimmenden Signatur. Methodenparameter können Delegat-Typen haben, so dass dieser einen Methode ein Verweis auf eine andere Methode übergeben wird, die dann aufgerufen werden kann
Func<...,TResult>
: Action<...>
, Predicate<T>
und Func<...,TResult>
Der System
Namespace enthält die Delegaten Action<...>
, Predicate<T>
und Func<...,TResult>
, wobei "..." generische Typparameter darstellt (für 0-Parameter ist Action
generisch).
Func
repräsentiert Methoden mit einem Rückgabetyp, der mit TResult
übereinstimmt, und Action
stellt Methoden ohne Rückgabewert dar (void). In beiden Fällen stimmen die zusätzlichen generischen Typparameter der Reihenfolge nach mit den Methodenparametern überein.
Predicate
für Methode mit booleschem Rückgabetyp, T ist Eingabeparameter.
Benutzerdefinierte Delegatentypen
Benannte Delegattypen können mit dem delegate
Schlüsselwort deklariert werden.
Delegierte aufrufen
Delegaten können mit derselben Syntax wie Methoden aufgerufen werden: Der Name der Delegateninstanz, gefolgt von Klammern, die Parameter enthalten.
Delegierten zuweisen
Delegierten können auf folgende Weise zugewiesen werden:
- Eine benannte Methode zuweisen
- Zuweisen einer anonymen Methode mit einem Lambda
- Zuweisen einer benannten Methode mit dem
delegate
Schlüsselwort
Delegierte kombinieren
Mit dem Operator +
können mehrere Delegatenobjekte einer Delegateninstanz zugewiesen werden. Mit dem Operator -
kann ein Komponentendelegat aus einem anderen Delegat entfernt werden.
Basiswerte der benannten Methodendelegaten
Wenn Sie Delegaten benannte Methoden zuweisen, beziehen sie sich auf dasselbe zugrunde liegende Objekt, wenn
Sie sind die gleiche Instanzmethode in derselben Instanz einer Klasse
Sie sind dieselbe statische Methode für eine Klasse
public class Greeter { public void WriteInstance() { Console.WriteLine("Instance"); } public static void WriteStatic() { Console.WriteLine("Static"); } } // ... Greeter greeter1 = new Greeter(); Greeter greeter2 = new Greeter(); Action instance1 = greeter1.WriteInstance; Action instance2 = greeter2.WriteInstance; Action instance1Again = greeter1.WriteInstance; Console.WriteLine(instance1.Equals(instance2)); // False Console.WriteLine(instance1.Equals(instance1Again)); // True Action @static = Greeter.WriteStatic; Action staticAgain = Greeter.WriteStatic; Console.WriteLine(@static.Equals(staticAgain)); // True
Delegatentyp deklarieren
Die folgende Syntax erstellt einen delegate
mit dem Namen NumberInOutDelegate
, ein Verfahren darstellt , welches einen nimmt int
und gibt eine int
.
public delegate int NumberInOutDelegate(int input);
Dies kann wie folgt verwendet werden:
public static class Program
{
static void Main()
{
NumberInOutDelegate square = MathDelegates.Square;
int answer1 = square(4);
Console.WriteLine(answer1); // Will output 16
NumberInOutDelegate cube = MathDelegates.Cube;
int answer2 = cube(4);
Console.WriteLine(answer2); // Will output 64
}
}
public static class MathDelegates
{
static int Square (int x)
{
return x*x;
}
static int Cube (int x)
{
return x*x*x;
}
}
Die example
Delegateninstanz wird auf dieselbe Weise wie die Square
Methode ausgeführt. Eine Delegateninstanz fungiert buchstäblich als Delegat für den Aufrufer: Der Aufrufer ruft den Delegaten auf, und der Delegat ruft die Zielmethode auf. Diese Umleitung entkoppelt den Aufrufer von der Zielmethode.
Sie können einen generischen Delegattyp deklarieren. In diesem Fall können Sie in einigen der Typargumente angeben, dass der Typ kovariant ( out
) oder contravariant ( in
) ist. Zum Beispiel:
public delegate TTo Converter<in TFrom, out TTo>(TFrom input);
Wie andere generische Typen können auch generische where TFrom : struct, IConvertible where TTo : new()
Einschränkungen aufweisen, z. B. where TFrom : struct, IConvertible where TTo : new()
.
Vermeiden Sie Ko- und Gegensätze für Delegatetypen, die für Multicast-Delegaten verwendet werden sollen, wie z. B. Ereignishandlertypen. Dies liegt daran, dass die Verkettung ( +
) fehlschlagen kann, wenn sich der Laufzeittyp aufgrund der Abweichung vom Kompilierzeittyp unterscheidet. Vermeiden Sie zum Beispiel:
public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);
Verwenden Sie stattdessen einen generischen invarianten Typ:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Unterstützt werden auch Delegierte, bei denen einige Parameter durch ref
oder out
geändert out
, wie in:
public delegate bool TryParser<T>(string input, out T result);
(Beispiel use TryParser<decimal> example = decimal.TryParse;
) oder Delegaten, bei denen der letzte Parameter den Parameter params
. Delegatetypen können optionale Parameter haben (Standardwerte für die Bereitstellung). Stellvertretertypen können Zeigertypen wie int*
oder char*
in ihren Signaturen oder Rückgabetypen verwenden (verwenden Sie ein unsafe
Schlüsselwort). Ein Delegattyp und seine Parameter können benutzerdefinierte Attribute enthalten.
Die Func , Aktion und Prädikat Delegiertypen
Der System-Namespace enthält Func<..., TResult>
mit generischen Parametern zwischen 0 und 15, wobei der Typ TResult
.
private void UseFunc(Func<string> func)
{
string output = func(); // Func with a single generic type parameter returns that type
Console.WriteLine(output);
}
private void UseFunc(Func<int, int, string> func)
{
string output = func(4, 2); // Func with multiple generic type parameters takes all but the first as parameters of that type
Console.WriteLine(output);
}
Der System-Namespace enthält außerdem Action<...>
Delegattypen mit einer unterschiedlichen Anzahl generischer Parameter (von 0 bis 16). Es ist ähnlich zu Func<T1, .., Tn>
, gibt aber immer void
.
private void UseAction(Action action)
{
action(); // The non-generic Action has no parameters
}
private void UseAction(Action<int, string> action)
{
action(4, "two"); // The generic action is invoked with parameters matching its type arguments
}
Predicate<T>
ist auch eine Form von Func
, aber es wird immer wieder zurückkehren bool
. Mit einem Prädikat können Sie benutzerdefinierte Kriterien angeben. Abhängig vom Wert der Eingabe und der im Prädikat definierten Logik gibt es entweder " true
oder " false
. Predicate<T>
verhält sich daher wie Func<T, bool>
und beide können initialisiert und auf dieselbe Weise verwendet werden.
Predicate<string> predicate = s => s.StartsWith("a");
Func<string, bool> func = s => s.StartsWith("a");
// Both of these return true
var predicateReturnsTrue = predicate("abc");
var funcReturnsTrue = func("abc");
// Both of these return false
var predicateReturnsFalse = predicate("xyz");
var funcReturnsFalse = func("xyz");
Die Wahl, ob Predicate<T>
oder Func<T, bool>
ist wirklich eine Func<T, bool>
. Predicate<T>
Func<T, bool>
die Absicht des Autors wohl mehr zum Ausdruck, während Func<T, bool>
wahrscheinlich einem größeren Teil der C # Func<T, bool>
ist.
Darüber hinaus gibt es einige Fälle, in denen nur eine der Optionen verfügbar ist, insbesondere bei der Interaktion mit einer anderen API. Beispielsweise verwenden List<T>
und Array<T>
Allgemeinen Predicate<T>
für ihre Methoden, während die meisten LINQ-Erweiterungen nur Func<T, bool>
akzeptieren.
Zuweisen einer benannten Methode zu einem Delegaten
Benannte Methoden können Delegierten mit übereinstimmenden Signaturen zugewiesen werden:
public static class Example
{
public static int AddOne(int input)
{
return input + 1;
}
}
Func<int,int> addOne = Example.AddOne
Example.AddOne
nimmt ein int
und gibt ein int
, dessen Signatur dem Delegaten Func<int,int>
. Example.AddOne
kann direkt zugeordnet werden addOne
weil sie passende Signaturen haben.
Gleichstellung delegieren
Das Aufrufen von .Equals()
für einen Delegaten vergleicht anhand der Referenzgleichheit:
Action action1 = () => Console.WriteLine("Hello delegates");
Action action2 = () => Console.WriteLine("Hello delegates");
Action action1Again = action1;
Console.WriteLine(action1.Equals(action1)) // True
Console.WriteLine(action1.Equals(action2)) // False
Console.WriteLine(action1Again.Equals(action1)) // True
Diese Regeln gelten auch für +=
oder -=
für einen Multicast-Delegierten, z. B. für das Abonnieren und Abbestellen von Ereignissen.
Zuweisung eines Delegierten durch Lambda
Mit Lambdas können anonyme Methoden erstellt werden, die einem Delegierten zugewiesen werden können:
Func<int,int> addOne = x => x+1;
Beachten Sie, dass die explizite Typdeklaration erforderlich ist, wenn Sie eine Variable auf diese Weise erstellen:
var addOne = x => x+1; // Does not work
Delegaten als Parameter übergeben
Delegierte können als typisierte Funktionszeiger verwendet werden:
class FuncAsParameters
{
public void Run()
{
DoSomething(ErrorHandler1);
DoSomething(ErrorHandler2);
}
public bool ErrorHandler1(string message)
{
Console.WriteLine(message);
var shouldWeContinue = ...
return shouldWeContinue;
}
public bool ErrorHandler2(string message)
{
// ...Write message to file...
var shouldWeContinue = ...
return shouldWeContinue;
}
public void DoSomething(Func<string, bool> errorHandler)
{
// In here, we don't care what handler we got passed!
...
if (...error...)
{
if (!errorHandler("Some error occurred!"))
{
// The handler decided we can't continue
return;
}
}
}
}
Delegierte kombinieren (Multicast-Delegierte)
Addition +
und Subtraktion -
Operationen können verwendet werden, um Delegierungsinstanzen zu kombinieren. Der Delegat enthält eine Liste der zugewiesenen Delegierten.
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace DelegatesExample {
class MainClass {
private delegate void MyDelegate(int a);
private static void PrintInt(int a) {
Console.WriteLine(a);
}
private static void PrintType<T>(T a) {
Console.WriteLine(a.GetType());
}
public static void Main (string[] args)
{
MyDelegate d1 = PrintInt;
MyDelegate d2 = PrintType;
// Output:
// 1
d1(1);
// Output:
// System.Int32
d2(1);
MyDelegate d3 = d1 + d2;
// Output:
// 1
// System.Int32
d3(1);
MyDelegate d4 = d3 - d2;
// Output:
// 1
d4(1);
// Output:
// True
Console.WriteLine(d1 == d4);
}
}
}
In diesem Beispiel ist d3
eine Kombination aus d1
und d2
Delegaten. Wenn das Programm aufgerufen wird, gibt es System.Int32
Zeichenfolgen 1
und System.Int32
.
Delegaten mit nicht ungültigen Rückgabetypen kombinieren:
Wenn ein Multicastdelegat einen Rückgabetyp ohne nonvoid
, erhält der Aufrufer den Rückgabewert von der zuletzt nonvoid
Methode. Die vorhergehenden Methoden werden weiterhin aufgerufen, ihre Rückgabewerte werden jedoch verworfen.
class Program
{
public delegate int Transformer(int x);
static void Main(string[] args)
{
Transformer t = Square;
t += Cube;
Console.WriteLine(t(2)); // O/P 8
}
static int Square(int x) { return x * x; }
static int Cube(int x) { return x*x*x; }
}
t(2)
ruft zuerst Square
und dann Cube
. Der Rückgabewert von Square wird verworfen und der Rückgabewert der letzten Methode, dh Cube
wird beibehalten.
Sichern Sie den Multicast-Delegaten
Wollten Sie schon immer einen Multicast-Delegaten anrufen, möchten Sie jedoch, dass die gesamte Anrufliste auch dann aufgerufen wird, wenn in der Kette eine Ausnahme auftritt. Dann haben Sie Glück, ich habe eine Erweiterungsmethode erstellt, die genau das tut und eine AggregateException
nur nach Ausführung der gesamten Liste auslöst:
public static class DelegateExtensions
{
public static void SafeInvoke(this Delegate del,params object[] args)
{
var exceptions = new List<Exception>();
foreach (var handler in del.GetInvocationList())
{
try
{
handler.Method.Invoke(handler.Target, args);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if(exceptions.Any())
{
throw new AggregateException(exceptions);
}
}
}
public class Test
{
public delegate void SampleDelegate();
public void Run()
{
SampleDelegate delegateInstance = this.Target2;
delegateInstance += this.Target1;
try
{
delegateInstance.SafeInvoke();
}
catch(AggregateException ex)
{
// Do any exception handling here
}
}
private void Target1()
{
Console.WriteLine("Target 1 executed");
}
private void Target2()
{
Console.WriteLine("Target 2 executed");
throw new Exception();
}
}
Dies gibt aus:
Target 2 executed
Target 1 executed
SaveInvoke
Aufrufen ohne SaveInvoke
würde nur Ziel 2 ausführen.
Schließung in einem Delegierten
Verschlüsse sind Inline - anonyme Methoden, die die Fähigkeit zu verwenden , haben Parent
Methode Variablen und andere anonyme Methoden , die in der übergeordneten Umfang definiert sind.
Im Wesentlichen ist ein Abschluss ein Codeblock, der zu einem späteren Zeitpunkt ausgeführt werden kann, jedoch die Umgebung beibehält, in der er erstellt wurde, dh er kann die lokalen Variablen usw. der Methode, die ihn erstellt hat, auch danach noch verwenden Methode wurde beendet. - Jon Skeet
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
Beispiel aus Closures in .NET .
Transformationen in Funktionen einkapseln
public class MyObject{
public DateTime? TestDate { get; set; }
public Func<MyObject, bool> DateIsValid = myObject => myObject.TestDate.HasValue && myObject.TestDate > DateTime.Now;
public void DoSomething(){
//We can do this:
if(this.TestDate.HasValue && this.TestDate > DateTime.Now){
CallAnotherMethod();
}
//or this:
if(DateIsValid(this)){
CallAnotherMethod();
}
}
}
Im Sinne einer sauberen Codierung kann das Einkapseln von Überprüfungen und Transformationen wie der oben genannten als Func Ihren Code leichter lesbar und verständlicher machen. Das obige Beispiel ist sehr einfach. Was wäre, wenn es mehrere DateTime-Eigenschaften mit jeweils unterschiedlichen Validierungsregeln gäbe und wir verschiedene Kombinationen prüfen wollten? Einfache, einzeilige Funcs, die jeweils über eine Rückgabelogik verfügen, können sowohl lesbar sein als auch die scheinbare Komplexität Ihres Codes reduzieren. Betrachten Sie die folgenden Func-Aufrufe und stellen Sie sich vor, wie viel mehr Code die Methode durcheinanderbringen würde:
public void CheckForIntegrity(){
if(ShipDateIsValid(this) && TestResultsHaveBeenIssued(this) && !TestResultsFail(this)){
SendPassingTestNotification();
}
}