In C#, not only are we able to declare a delegate, but we are also able to use the built-in delegate from the C# standard library. This built-in delegate also applies to the generic data type, so let's discuss the generic delegate prior to discussing the built-in delegate.
A delegate type can use a generic type as its parameter. Using the generic type, we can put off the specification of one or more types in parameters or return values until the delegate is initialized into a variable. In other words, we do not specify the data types of the delegate's parameters and return values when we define a delegate type. To discuss this in more detail, let's take a look at the following code, which we can find at GenericDelegates.csproj
:
public partial class Program { private delegate T FormulaDelegate<T>(T a, T b); }
We have a delegate name, FormulaDelegate
, using the generic data type. As we can see, there is a T
symbol, which represents the data type we will define when declaring the variable typed FormulaDelegate
. We continue by adding the following two methods that have completely different signatures:
public partial class Program { private static int AddInt(int x, int y) { return x + y; } private static double AddDouble(double x, double y) { return x + y; } }
Now let's take a look at the following code in order to explain how we declare the variable-typed delegate and invoke the method from the delegate:
public partial class Program { private static void GenericDelegateInvoke() { FormulaDelegate<int> intAddition = AddInt; FormulaDelegate<double> doubleAddition = AddDouble; Console.WriteLine("Invoking intAddition(2, 3)"); Console.WriteLine( "Result = {0}", intAddition(2, 3)); Console.WriteLine("Invoking doubleAddition(2.2, 3.5)"); Console.WriteLine( "Result = {0}", doubleAddition(2.2, 3.5)); } }
The following result will be displayed in the console when we run the GenericDelegateInvoke()
method:
From the preceding code, we can declare two methods that have different signature using only one delegate type. The intAddition
delegate refers to the AddInt()
method, which applies the int
data type in its parameters and return value, while the doubleAddition
delegate refers to the AddDouble()
method, which applies the double
data type in its parameters and return value. However, in order for the delegate to know the data type of the method it refers, we have to define the data type in angular brackets (<>
) when we initialize the delegate. The following code snippet is the delegate initialization that uses the generic data type (symbolized by the angular brackets):
FormulaDelegate<int> intAddition = AddInt; FormulaDelegate<double> doubleAddition = AddDouble;
Because we have defined the data type, the delegate can match the data type of the method it refers. That's why, from the output console, we can invoke the two methods that have different signatures.
We have successfully used a generic type for delegates, applying one generic template. The following code, which we can find at MultiTemplateDelegates.csproj
, shows us that the delegate can also apply the multigeneric template in one delegate declaration:
public partial class Program { private delegate void AdditionDelegate<T1, T2>( T1 value1, T2 value2); }
The preceding code will create a new delegate named AdditionDelegate
, which has two parameters with two different data types. T1
and T2
represent the data type that will be defined in the variable-typed delegate declaration. Now, let's create two methods that have different signatures, as follows:
public partial class Program { private static void AddIntDouble(int x, double y) { Console.WriteLine( "int {0} + double {1} = {2}", x, y, x + y); } private static void AddFloatDouble(float x, double y) { Console.WriteLine( "float {0} + double {1} = {2}", x, y, x + y); } }
To refer the AdditionDelegate
delegate to the AddIntDouble()
and AddFloatDouble()
methods and invoke the delegate, we can create the VoidDelegateInvoke()
method, as follows:
public partial class Program { private static void VoidDelegateInvoke() { AdditionDelegate<int, double> intDoubleAdd = AddIntDouble; AdditionDelegate<float, double> floatDoubleAdd = AddFloatDouble; Console.WriteLine("Invoking intDoubleAdd delegate"); intDoubleAdd(1, 2.5); Console.WriteLine("Invoking floatDoubleAdd delegate"); floatDoubleAdd((float)1.2, 4.3); } }
If we run the VoidDelegateInvoke()
method, we will see the following output on our console:
From the preceding console output, it can be seen that we have successfully invoked the intDoubleAdd
and floatDoubleAdd
delegates although they have different method signatures. This is possible since we apply the T1
and T2
template in the AdditionDelegate
delegate.
Let's try to create the multitemplate delegate again, but this time, we use the method that has a return value. The declaration of the delegate will be as follows:
public partial class Program { private delegate TResult AddAndConvert<T1, T2, TResult>( T1 digit1, T2 digit2); }
Then, we add the two methods AddIntDoubleConvert()
and AddFloatDoubleConvert()
to our project:
public partial class Program { private static float AddIntDoubleConvert(int x, double y) { float result = (float)(x + y); Console.WriteLine( "(int) {0} + (double) {1} = (float) {2}", x, y, result); return result; } private static int AddFloatDoubleConvert(float x, double y) { int result = (int)(x + y); Console.WriteLine( "(float) {0} + (double) {1} = (int) {2}", x, y, result); return result; } }
In order to use the AddAndConvert
delegate, we can create the ReturnValueDelegateInvoke()
method, as follows:
public partial class Program { private static void ReturnValueDelegateInvoke() { AddAndConvert<int, double, float> intDoubleAddConvertToFloat = AddIntDoubleConvert; AddAndConvert<float, double, int> floatDoubleAddConvertToInt = AddFloatDoubleConvert; Console.WriteLine("Invoking intDoubleAddConvertToFloat delegate"); float f = intDoubleAddConvertToFloat(5, 3.9); Console.WriteLine("Invoking floatDoubleAddConvertToInt delegate"); int i = floatDoubleAddConvertToInt((float)4.3, 2.1); } }
When we invoke the ReturnValueDelegateInvoke()
method, we get the following output:
Again, we successfully invoke the two different signature methods using a multitemplate generic type.
Let's go back to the following delegate declaration we discussed earlier in the chapter:
public partial class Program { private delegate void AdditionDelegate<T1, T2>( T1 value1, T2 value2); }
C# has a built-in delegate that can take a maximum of 16 parameters and return void. It is called the Action
delegate. In other words, the Action
delegate will point to a method that return nothing and takes zero, one, or more input parameters. Due to the existence of the Action
delegate, we no longer need to declare a delegate, and we can immediately assign any method to the delegate. We can modify the preceding MultiTemplateDelegates.csproj
project and remove the AdditionDelegate
delegate since we will now use the Action
delegate. Then, the ActionDelegateInvoke()
method in MultiTemplateDelegates.csproj
will be modified to become ActionDelegateInvoke()
with the following implementation:
public partial class Program { private static void ActionDelegateInvoke() { Action<int, double> intDoubleAddAction = AddIntDouble; Action<float, double> floatDoubleAddAction = AddFloatDouble; Console.WriteLine( "Invoking intDoubleAddAction delegate"); intDoubleAddAction(1, 2.5); Console.WriteLine( "Invoking floatDoubleAddAction delegate"); floatDoubleAddAction((float)1.2, 4.3); } }
We can find the preceding code in the ActionFuncDelegates.csproj
project. As we can see, now we apply the Action
delegate to replace the AdditionDelegate
delegate in the MultiTemplateDelegates.csproj
project, as follows:
Action<int, double> intDoubleAddAction = AddIntDouble; Action<float, double> floatDoubleAddAction = AddFloatDouble;
C# has another built-in delegate that has a return value by taking a maximum of 16 parameters. They are Func
delegates. Let's go back to the MultiTemplateDelegates.csproj
project and find the following delegate:
public partial class Program { private delegate TResult AddAndConvert<T1, T2, TResult>( T1 digit1, T2 digit2); }
We can remove the preceding delegate since it matches the declaration of the Func
delegate. So, we can modify the ReturnValueDelegateInvoke()
method in the MultiTemplateDelegates.csproj
project for it to become the FuncDelegateInvoke()
method with the following implementation:
public partial class Program { private static void FuncDelegateInvoke() { Func<int, double, float> intDoubleAddConvertToFloatFunc = AddIntDoubleConvert; Func<float, double, int> floatDoubleAddConvertToIntFunc = AddFloatDoubleConvert; Console.WriteLine( "Invoking intDoubleAddConvertToFloatFunc delegate"); float f = intDoubleAddConvertToFloatFunc(5, 3.9); Console.WriteLine( "Invoking floatDoubleAddConvertToIntFunc delegate"); int i = floatDoubleAddConvertToIntFunc((float)4.3, 2.1); } }
Now, we no longer need the AddAndConvert
delegate anymore since we have applied the Func
delegate, as follows:
Func<int, double, float> intDoubleAddConvertToFloatFunc = AddIntDoubleConvert; Func<float, double, int> floatDoubleAddConvertToIntFunc = AddFloatDoubleConvert;
Using the Action
and Func
built-in delegates, the code becomes shorter and the definition of the delegate becomes easier and quicker.