Sök…


Anmärkningar

Sammanfattning

En delegattyp är en typ som representerar en viss metodsignatur. En förekomst av denna typ avser en viss metod med en matchande signatur. Metodparametrar kan ha delegattyper, och så den här metoden som ska skickas en referens till en annan metod, som sedan kan åberopas

Func<...,TResult> : Action<...> , Predicate<T> och Func<...,TResult>

Den System namnutrymmet innehåller Action<...> , Predicate<T> och Func<...,TResult> delegater, där "..." representerar mellan 0 och 16 generiska typ parametrar (0 parametrar, Action är icke- generisk).

Func representerar metoder med en TResult som matchar TResult , och Action representerar metoder utan returvärde (tom). I båda fallen matchar de extra generiska parametrarna i ordning metodparametrarna.

Predicate representerar metod med boolesk returtyp, T är ingångsparameter.

Anpassade delegattyper

Namngivna delegattyper kan deklareras med hjälp av delegate .

Åkallar delegater

Delegater kan åberopas med samma syntax som metoder: namnet på delegatinstansen, följt av parenteser som innehåller några parametrar.

Tilldela delegater

Delegater kan tilldelas på följande sätt:

  • Tilldela en namngiven metod
  • Tilldela en anonym metod med hjälp av en lambda
  • Tilldela en namngiven metod med hjälp av delegate nyckelord.

Kombinera delegater

Flera delegerade objekt kan tilldelas en delegerad instans med hjälp av + -operatören. Den - operatören kan användas för att avlägsna en komponent delegat från en annan delegat.

Underliggande referenser av namngivna metoddelegater

Vid tilldelning av namngivna metoder till delegerade hänvisar de till samma underliggande objekt om:

  • De är samma instansmetod, i samma instans av en klass

  • De är samma statiska metod i en klass

    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
    

Förklarar en delegatyp

Följande syntax skapar en delegate typ med känt NumberInOutDelegate , som representerar en metod som tar ett int och returnerar en int .

public delegate int NumberInOutDelegate(int input);

Detta kan användas på följande sätt:

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

example delegerad instans körs på samma sätt som Square metoden. En delegerad instans fungerar bokstavligen som en delegat för den som ringer: den som ringer åkallar delegaten och sedan kallar delegaten målmetoden. Denna indirekt frikopplar den som ringer från målmetoden.


Du kan förklara en generisk delegatyp och i så fall kan du ange att typen är kovariant ( out ) eller contravariant ( in ) i några av typargumenten. Till exempel:

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

Liksom andra generiska typer kan generiska delegattyper ha begränsningar, till exempel where TFrom : struct, IConvertible where TTo : new() .

Undvik co- och contravariance för delegattyper som är avsedda att användas för multicast-delegater, till exempel typer av evenemangshanterare. Detta beror på att sammankoppling ( + ) kan misslyckas om körtidstypen skiljer sig från kompileringstiden på grund av variansen. Undvik till exempel:

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

Använd istället en invariant generisk typ:

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

Dessutom stöds delegater där vissa parametrar ändras genom ref eller out , som i:

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

(provet använder TryParser<decimal> example = decimal.TryParse; ), eller delegerar där den sista parametern har params modifieraren. Delegattyper kan ha valfria parametrar (leverera standardvärden). Delegerade typer kan använda pekartyper som int* eller char* i sina signaturer eller returtyper (använd unsafe nyckelord). En delegattyp och dess parametrar kan bära anpassade attribut.

Func , Åtgärd och predikat delegattyper

Systemets namnutrymme innehåller Func<..., TResult> med mellan 0 och 15 generiska parametrar, och returnerar 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);
}

Systemets namnutrymme innehåller också Action<...> delegattyper med olika antal generiska parametrar (från 0 till 16). Det liknar Func<T1, .., Tn> , men det återgår alltid 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> är också en form av Func men det kommer alltid att returnera bool . Ett predikat är ett sätt att specificera anpassade kriterier. Beroende på värdet på ingången och logiken definierad i predikatet kommer den att returnera antingen true eller false . Predicate<T> fungerar därför på samma sätt som Func<T, bool> och båda kan initieras och användas på samma sätt.

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

Valet av att använda Predicate<T> eller Func<T, bool> är verkligen en åsiktfråga. Predicate<T> är utan tvekan mer uttrycksfullt av författarens avsikt, medan Func<T, bool> troligen kommer att vara bekant för en större andel av C # -utvecklarna.

Utöver det finns det några fall där endast ett av alternativen är tillgängliga, särskilt när du interagerar med ett annat API. Till exempel tar List<T> och Array<T> vanligtvis Predicate<T> för sina metoder, medan de flesta LINQ-tillägg bara accepterar Func<T, bool> .

Tilldela en namngiven metod till en delegat

Namngivna metoder kan tilldelas delegater med matchande signaturer:

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


Func<int,int> addOne = Example.AddOne

Example.AddOne tar ett int och returnerar ett int , dess signatur matchar delegaten Func<int,int> . Example.AddOne kan direkt tilldelas addOne eftersom de har matchande signaturer.

Delegera jämställdhet

Calling .Equals() på en delegat jämför med jämställdhet:

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

Dessa regler gäller också när du gör += eller -= på en multicast-delegat, till exempel när du prenumererar och avbokar från evenemang.

Tilldelar en delegat av lambda

Lambdas kan användas för att skapa anonyma metoder att tilldela en delegat:

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

Observera att den uttryckliga typdeklarationen krävs när du skapar en variabel på detta sätt:

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

Passera delegater som parametrar

Delegater kan användas som typfunktionspekare:

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

Kombinera delegater (Multicast-delegater)

Addition + och subtraktion - operationer kan användas för att kombinera delegerade instanser. Delegaten innehåller en lista över de tilldelade delegaterna.

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

I detta exempel d3 är en kombination av d1 och d2 delegater, så när som kallas programmet utgångar både 1 och System.Int32 strängar.


Kombinera delegater med icke ogiltiga returtyper:

Om en multicast-delegat har en nonvoid får den som ringer returvärdet från den sista metoden som ska åberopas. De föregående metoderna kallas fortfarande, men deras returvärden kasseras.

    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) ringer först Square och sedan Cube . Returvärdet för Square kastas och returvärdet för den sista metoden dvs Cube bibehålls.

Safe säkerställa multicast-delegat

Har någonsin velat ringa en multicast-delegat men du vill att hela invokationslistan ska ringas även om ett undantag inträffar i någon i kedjan. Då har du tur, jag har skapat en förlängningsmetod som gör just det, kasta en AggregateException först efter att körningen av hela listan är klar:

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

Denna utgångar:

Target 2 executed
Target 1 executed

Åkallande direkt utan SaveInvoke skulle bara köra mål 2.

Stängning inom en delegat

Stängningar är inonyma anonyma metoder som har förmågan att använda Parent och andra anonyma metoder som definieras i föräldrarnas omfattning.

I huvudsak är en stängning ett kodblock som kan köras vid ett senare tillfälle, men som upprätthåller den miljö där den först skapades - dvs den kan fortfarande använda de lokala variablerna etc i den metod som skapade den, även efter det metoden har slutförts. - Jon Skeet

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

}

Exempel taget från Stängningar i .NET .

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

I en anda av ren kodning kan kapsling av kontroller och transformationer som ovanstående som en Func göra din kod lättare att läsa och förstå. Medan exemplet ovan är väldigt enkelt, men om det fanns flera DateTime-egenskaper, var och en med sina olika olika valideringsregler och vi ville kontrollera olika kombinationer? Enkla funktionsfunktioner i en rad som var och en har etablerad returlogik kan vara både läsbara och minska den uppenbara komplexiteten för din kod. Tänk på nedanstående Func-samtal och föreställ dig hur mycket mer kod som skulle röra upp metoden:

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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow