Zoeken…


Opmerkingen

Samenvatting

Een gedelegeerd type is een type dat een bepaalde methodehandtekening vertegenwoordigt. Een exemplaar van dit type verwijst naar een bepaalde methode met een bijpassende handtekening. Methodeparameters kunnen gedelegeerde typen hebben, en dus moet deze ene methode worden doorgegeven aan een andere methode, die vervolgens kan worden aangeroepen

Ingebouwde delegatietypen: Action<...> , Predicate<T> en Func<...,TResult>

De System namespace bevat Action<...> , Predicate<T> en Func<...,TResult> gedelegeerden, waar de "..." staat tussen de 0 en 16 algemeen type parameters (voor 0 parameters, Action is niet algemeen).

Func vertegenwoordigt methoden met een TResult overeenkomt met TResult , en Action vertegenwoordigt methoden zonder retourwaarde (ongeldig). In beide gevallen komen de aanvullende generieke typeparameters in volgorde overeen met de methodeparameters.

Predicate vertegenwoordigt de methode met het booleaanse type, T is invoerparameter.

Aangepaste delegatietypen

Genoemde gedelegeerde typen kunnen worden gedeclareerd met behulp van het delegate trefwoord.

Afgevaardigden aanroepen

Afgevaardigden kunnen worden opgeroepen met dezelfde syntaxis als methoden: de naam van de gemachtigde-instantie, gevolgd door haakjes die eventuele parameters bevatten.

Toewijzen aan afgevaardigden

Afgevaardigden kunnen op de volgende manieren worden toegewezen:

  • Een benoemde methode toewijzen
  • Een anonieme methode toewijzen met behulp van een lambda
  • Een benoemde methode toewijzen met het trefwoord delegate .

Afgevaardigden combineren

U kunt meerdere gemachtigde objecten toewijzen aan één gemachtigde instantie met behulp van de operator + . De - operator kunnen worden gebruikt om een component van een andere gedelegeerde gedelegeerde verwijderen.

Onderliggende referenties van genoemde methodedeelnemers

Bij het toewijzen van benoemde methoden aan gedelegeerden, verwijzen ze naar hetzelfde onderliggende object als:

  • Ze zijn dezelfde instantiemethode, op dezelfde instance van een klasse

  • Ze zijn dezelfde statische methode voor een 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
    

Afgevaardigde type verklaren

De volgende syntax wordt een delegate soort met de naam NumberInOutDelegate , die een werkwijze waarbij een draait int en retourneert een int .

public delegate int NumberInOutDelegate(int input);

Dit kan als volgt worden gebruikt:

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

Het example gedelegeerde instantie wordt uitgevoerd op dezelfde wijze als de Square methode. Een afgevaardigdeninstantie fungeert letterlijk als afgevaardigde voor de beller: de beller roept de afgevaardigde aan en vervolgens roept de afgevaardigde de doelmethode aan. Deze aanwijzing ontkoppelt de beller van de doelmethode.


U kunt een generiek gedelegeerde type opgeven en in dat geval kunt u opgeven dat het type covariant ( out ) of contravariant ( in ) is in sommige van de type argumenten. Bijvoorbeeld:

public delegate TTo Converter<in TFrom, out TTo>(TFrom input);

Net als andere generieke typen kunnen generieke gedelegeerde typen beperkingen hebben, zoals where TFrom : struct, IConvertible where TTo : new() .

Vermijd co- en tegenstrijdigheid voor gedelegeerde typen die bedoeld zijn om te worden gebruikt voor multicast-gedelegeerden, zoals typen gebeurtenishandlers. Dit komt omdat aaneenschakeling ( + ) kan mislukken als het runtime-type verschilt van het compilatie-type vanwege de variantie. Vermijd bijvoorbeeld:

public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);

Gebruik in plaats daarvan een invariant generiek type:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

Ook worden afgevaardigden ondersteund waar sommige parameters worden gewijzigd door ref of out , zoals in:

public delegate bool TryParser<T>(string input, out T result);

(gebruik sample TryParser<decimal> example = decimal.TryParse; ) of delegeer waar de laatste parameter de modificatie params . Delegatietypen kunnen optionele parameters hebben (standaardwaarden leveren). Delegate types kunnen pointer types zoals int* of char* in hun handtekeningen of return types (gebruik een unsafe trefwoord). Een gedelegeerd type en de bijbehorende parameters kunnen aangepaste kenmerken bevatten.

The Func , Actie en predicaat soorten deelnemers

De Func<..., TResult> bevat Func<..., TResult> delegatietypen met tussen 0 en 15 generieke parameters, met als TResult type 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);
}

De systeemnaamruimte bevat ook Action<...> delegatietypen met een verschillend aantal generieke parameters (van 0 tot 16). Het is vergelijkbaar met Func<T1, .., Tn> , maar het retourneert altijd 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> is ook een vorm van Func , maar het zal altijd terugkeren bool . Een predicaat is een manier om aangepaste criteria op te geven. Afhankelijk van de waarde van de invoer en de logica die binnen het predikaat is gedefinieerd, retourneert deze true of false . Predicate<T> gedraagt zich daarom op dezelfde manier als Func<T, bool> en beide kunnen worden geïnitialiseerd en op dezelfde manier worden gebruikt.

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");

De keuze om Predicate<T> of Func<T, bool> is echt een kwestie van mening. Predicate<T> drukt aantoonbaar meer de bedoeling van de auteur uit, terwijl Func<T, bool> waarschijnlijk een groter deel van de C # -ontwikkelaars kent.

Daarnaast zijn er enkele gevallen waarin slechts één van de opties beschikbaar is, vooral bij interactie met een andere API. List<T> en Array<T> nemen bijvoorbeeld meestal Predicate<T> voor hun methoden, terwijl de meeste LINQ-extensies alleen Func<T, bool> accepteren.

Een benoemde methode toewijzen aan een gemachtigde

Benoemde methoden kunnen worden toegewezen aan deelnemers met overeenkomende handtekeningen:

public static class Example
{
    public static int AddOne(int input)
    {
        return input + 1;
    }
}


Func<int,int> addOne = Example.AddOne

Example.AddOne neemt een int en retourneert een int , de handtekening komt overeen met de gemachtigde Func<int,int> . Example.AddOne addOne kan direct worden toegewezen aan addOne omdat ze overeenkomende handtekeningen hebben.

Gelijkheid delegeren

Calling .Equals() op een gemachtigde vergelijkt op referentiegelijkheid:

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

Deze regels zijn ook van toepassing bij het doen van += of -= op een multicast-gemachtigde, bijvoorbeeld bij het in- en uitschrijven van evenementen.

Toekenning aan een gemachtigde door lambda

Lambdas kan worden gebruikt om anonieme methoden te maken om aan een gemachtigde toe te wijzen:

Func<int,int> addOne = x => x+1;

Merk op dat de expliciete type-verklaring vereist is bij het maken van een variabele op deze manier:

var addOne = x => x+1; // Does not work

Afgevaardigden doorgeven als parameters

Afgevaardigden kunnen worden gebruikt als getypte functiewijzers:

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

Afgevaardigden combineren (Multicast-afgevaardigden)

Optellen + en aftrekken - bewerkingen kunnen worden gebruikt om instanties van deelnemers te combineren. De gedelegeerde bevat een lijst van de toegewezen afgevaardigden.

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 dit voorbeeld is d3 een combinatie van d1 en d2 afgevaardigden, dus wanneer het programma wordt uitgevoerd, worden zowel 1 als System.Int32 strings uitgevoerd.


Afgevaardigden combineren met niet-ongeldige retourtypen:

Als een multicast-afgevaardigde een niet- nonvoid heeft, ontvangt de beller de retourwaarde van de laatst aan te roepen methode. De voorgaande methoden worden nog steeds aangeroepen, maar hun retourwaarden worden genegeerd.

    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) roept eerst Square en vervolgens Cube . De geretourneerde waarde van Square wordt genegeerd en de geretourneerde waarde van de laatste methode, dwz Cube wordt behouden.

Veilig multicast-gemachtigde aanroepen

Ooit een multicast-afgevaardigde willen aanroepen, maar u wilt dat de hele invocatielijst wordt opgeroepen, zelfs als er een uitzondering in de keten optreedt. Dan heb je geluk, ik heb een extensiemethode gemaakt die precies dat doet, een AggregateException gooien alleen nadat de uitvoering van de hele lijst is voltooid:

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

Dit geeft uit:

Target 2 executed
Target 1 executed

Direct aanroepen, zonder SaveInvoke , zou alleen Doel 2 uitvoeren.

Sluiting binnen een afgevaardigde

Sluitingen zijn inline anonieme methoden die de mogelijkheid bieden om Parent en andere anonieme methoden te gebruiken die binnen het bereik van de ouder zijn gedefinieerd.

In wezen is een afsluiting een codeblok dat op een later tijdstip kan worden uitgevoerd, maar dat de omgeving handhaaft waarin het voor het eerst is gemaakt - dat wil zeggen dat het nog steeds de lokale variabelen enz. Van de methode die het heeft gemaakt kan gebruiken, zelfs daarna methode is voltooid. - Jon Skeet

delegate int testDel();
static void Main(string[] args)
{
    int foo = 4;
    testDel myClosure = delegate()
    {
        return foo;
    };
    int bar = myClosure();

}

Voorbeeld uit afsluitingen in .NET .

Transformaties inkapselen in funcs

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

In de geest van schone codering kunnen ingekapselde controles en transformaties zoals die hierboven als een Func uw code gemakkelijker leesbaar en begrijpelijk maken. Hoewel het bovenstaande voorbeeld heel eenvoudig is, wat als er meerdere DateTime-eigenschappen waren met elk hun eigen verschillende validatieregels en we verschillende combinaties wilden controleren? Eenvoudige Functies met één regel die elk een retourlogica hebben, kunnen zowel leesbaar zijn als de schijnbare complexiteit van uw code verminderen. Beschouw de onderstaande Func-aanroepen en stel je voor hoeveel meer code de methode zou overbelasten:

public void CheckForIntegrity(){
    if(ShipDateIsValid(this) && TestResultsHaveBeenIssued(this) && !TestResultsFail(this)){
        SendPassingTestNotification();
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow