C# Language
delegater
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();
}
}