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();
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow