Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
57
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

Chapter 96: Delegates

Section 96.1: Declaring a delegate type

The following syntax creates a delegate type with name NumberInOutDelegate, representing a method which takes an int and returns an int.

public delegate int NumberInOutDelegate(int input);

This can be used as follows:

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;

}

}

The example delegate instance is executed in the same way as the Square method. A delegate instance literally acts as a delegate for the caller: the caller invokes the delegate, and then the delegate calls the target method. This indirection decouples the caller from the target method.

You can declare a generic delegate type, and in that case you may specify that the type is covariant (out) or contravariant (in) in some of the type arguments. For example:

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

Like other generic types, generic delegate types can have constraints, such as where TFrom : struct, IConvertible where TTo : new().

Avoid coand contravariance for delegate types that are meant to be used for multicast delegates, such as event handler types. This is because concatenation (+) can fail if the run-time type is di erent from the compile-time type because of the variance. For example, avoid:

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

GoalKicker.com – C# Notes for Professionals

521

Instead, use an invariant generic type:

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

Also supported are delegates where some parameters are modified by ref or out, as in:

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

(sample use TryParser<decimal> example = decimal.TryParse;), or delegates where the last parameter has the params modifier. Delegate types can have optional parameters (supply default values). Delegate types can use pointer types like int* or char* in their signatures or return types (use unsafe keyword). A delegate type and its parameters can carry custom attributes.

Section 96.2: The Func<T, TResult>, Action<T> and Predicate<T> delegate types

The System namespace contains Func<..., TResult> delegate types with between 0 and 15 generic parameters, returning 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);

}

The System namespace also contains Action<...> delegate types with di erent number of generic parameters

(from 0 to 16). It is similar to Func<T1, .., Tn>, but it always returns 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 also a form of Func but it will always return bool. A predicate is a way of specifying a custom

criteria. Depending on the value of the input and the logic defined within the predicate, it will return either true or false. Predicate<T> therefore behaves in the same way as Func<T, bool> and both can be initialized and used in the same way.

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

GoalKicker.com – C# Notes for Professionals

522

// Both of these return false

var predicateReturnsFalse = predicate("xyz"); var funcReturnsFalse = func("xyz");

The choice of whether to use Predicate<T> or Func<T, bool> is really a matter of opinion. Predicate<T> is arguably more expressive of the author's intent, while Func<T, bool> is likely to be familiar to a greater proportion of C# developers.

In addition to that, there are some cases where only one of the options is available, especially when interacting with another API. For example List<T> and Array<T> generally take Predicate<T> for their methods, while most LINQ extensions only accept Func<T, bool>.

Section 96.3: Combine Delegates (Multicast Delegates)

Addition + and subtraction - operations can be used to combine delegate instances. The delegate contains a list of the assigned delegates.

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

GoalKicker.com – C# Notes for Professionals

523

}

}

}

In this example d3 is a combination of d1 and d2 delegates, so when called the program outputs both 1 and

System.Int32 strings.

Combining delegates with non void return types:

If a multicast delegate has a nonvoid return type, the caller receives the return value from the last method to be invoked. The preceding methods are still called, but their return values are discarded.

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) will call first Square and then Cube. The return value of Square is discarded and return value of the last method i.e. Cube is retained.

Section 96.4: Safe invoke multicast delegate

Ever wanted to call a multicast delegate but you want the entire invokation list to be called even if an exception occurs in any in the chain. Then you are in luck, I have created an extension method that does just that, throwing an AggregateException only after execution of the entire list completes:

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

}

GoalKicker.com – C# Notes for Professionals

524