- Introducing Delegates
- Anonymous Methods
- System-Defined Delegates: Func<>
- Lambda Expressions
- Summary
System-Defined Delegates: Func<>
.NET 3.5 (C# 3.0) included a series of generic delegates with the names "Action" and "Func." System.Func represents delegates that had return types while System.Action corresponds when no return type occurs. The signatures for these delegates are shown in Listing 12.13 (although the in/out type modifiers were not added until C# 4.0, as discussed shortly).
Listing 12.13. Func and Action Delegate Declarations
public delegate void Action (); public delegate void Action<in T>(T arg) public delegate void Action<in T1, in T2>( in T1 arg1, in T2 arg2) public delegate void Action<in T1, in T2, in T3>( T1 arg1, T2 arg2, T3 arg3) public delegate void Action<in T1, in T2, in T3, in T4( T1 arg1, T2 arg2, T3 arg3, T4 arg4) ... public delegate void Action< in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T16( T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) public delegate TResult Func<out TResult>(); public delegate TResult Func<in T, out TResult>(T arg) public delegate TResult Func<in T1, in T2, out TResult>( in T1 arg1, in T2 arg2) public delegate TResult Func<in T1, in T2, in T3, out TResult>( T1 arg1, T2 arg2, T3 arg3) public delegate TResult Func<in T1, in T2, in T3, in T4, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4) ... public delegate TResult Func< in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T16, out TResult>( T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16)
Since the delegate definitions in Listing 12.13 are generic, it is possible to use them instead of defining a custom delegate. For example, rather than declaring the ComparisonHandler delegate type, code could simply declare ComparisonHandler delegates using Func<int, int, bool>. The last type parameter of Func is always the return type of the delegate. The earlier type parameters correspond in sequence to the type of delegate parameters. In the case of ComparisonHandler, the return is bool (the last type parameter of the Func declaration) and the type arguments int and int correspond with the first and second parameters of ComparisonHandler. In many cases, the inclusion of Func delegates in the .NET 3.5 Framework eliminates the necessity to define delegates. You should use System.Action, or one of the generic versions, for delegates that have no return (TResult) and that take no parameters.
However, you should still declare delegate types when such a type would simplify coding with the delegate. For example, continuing to use the ComparisonHandler provides a more explicit indication of what the delegate is used for, whereas Func<int, int, bool> provides nothing more than an understanding of the method signature.
Evaluation about whether to declare a delegate is still meaningful and includes considerations such as whether the name of the delegate identifier is sufficient for indicating intent, whether the delegate type name would clarify its use, and whether the use of a .NET 3.5 type will limit the use of the assembly to .NET 3.5 clients unnecessarily.
Note that even though you can use a generic Func delegate in place of an explicitly defined delegate, the types are not compatible. You cannot assign one delegate type to a variable of another delegate type even if the type parameters match. For example, you cannot assign a ComparisonHandler variable to a Func<int, int, bool> variable or pass them interchangeably as parameters even though both represent signatures for a delegate that takes two int parameters and returns a bool.
However, notice the type parameter modifiers decorating the delegates in Listing 12.13. These do allow for some degree of casting between them, thanks to the variance support added in C# 4.0. Consider the following contravariant example: Because void Action<in T>(T arg) has the in type parameter decorator, it is possible to assign type Action<string> an object of type Action<object>. In other words, any methods with a void return and an object parameter will implicitly cast to the more restrictive delegate type that only allows parameters of type string. Similarly with covariance and a Func delegate—since TResult Func<out TResult>() includes the out type parameter modifier on TResult, it is possible to implicitly assign a Func<object> variable the value of a Func<string>. (See Listing 12.14.)
Listing 12.14. Using Variance for Delegates
// Contravariance Action<object> broadAction = delegate(object data) { Console.WriteLine(data); }; Action<string> narrowAction = broadAction; // Contravariance Func<string> narrowFunction = delegate() { return Console.ReadLine(); }; Func<object> broadFunction = narrowFunction; // Contravariance & Covariance Combined Func<object, string> func1 = delegate(object data) { return data.ToString(); }; Func<string, object> func2 = func1;
The last part of the listing combines both variance concepts into a single example, demonstrating how they can occur simultaneously if both in and out type parameters are involved.
The need for variance support within these generic delegates was a key contributing factor for why C# now includes the feature.2