Thursday, 5 May 2011

Delegates 101 - Part III: Generic Delegates


In Part II we looked at how we can use anonymous methods and lambdas to pass in-line method implementations to a method which takes a delegate as a parameter.

In this article, we will look at how we can leverage the power of generics in order to make our delegates more ...well ...generic!

Generic Delegates

Continuing with our maths theme, let's look again at our original MathFunction delegate:

// C#
delegate int MathFunction(int int1, int int2);
' Visual Basic
Delegate Function MathFunction(ByVal int1 As Integer, ByVal int2 As Integer) As Integer

Well that is fine, providing all we ever want to work with are integers! In the real world, however, we are probably going to want to perform mathematical operations on all sorts of different data types. By use of a generic delegate, we can have a single delegate which will handle any data type we like in a type-safe manner:

// C#
delegate TResult MathFunction<T1, T2, TResult>(T1 var1, T2 var2);
' Visual Basic
Delegate Function MathFunction(Of T1, T2, TResult)(ByVal var1 As T1, ByVal var2 As T2) As TResult

Our delegate will now accept parameters of any arbitrary type and return a result of any arbitrary type.

Next, we need to modify our PrintResult() method accordingly to handle our new delegate:

// C#
static void PrintResult<T1, T2, TResult>(MathFunction<T1, T2, TResult> mathFunction, T1 var1, T2 var2)
    TResult result = mathFunction(var1, var2);
    Console.WriteLine(String.Format("Result is {0}", result));
' Visual Basic
Sub PrintResult(Of T1, T2, TResult)(ByVal mathFunction As MathFunction(Of T1, T2, TResult), ByVal var1 As T1, ByVal var2 As T2)
    Dim result As TResult = mathFunction(var1, var2)
    Console.WriteLine(String.Format("Result is {0}", result))
End Sub

Our simple calculator should now be able to perform mathematical operations on any data type of our choosing. Here are some examples:

// C#
PrintResult((x, y) => x / y, 5, 2); // Integer division - Result: 2
PrintResult((x, y) => x / y, 5, 2.0); // Real division - Result: 2.5
PrintResult((x, y) => 2 * y * x, 25, Math.PI); // Circumference of a circle - Result: 157.07963267949
PrintResult((x, y) => y * Math.Pow(x, 2), 25, Math.PI); // Area of circle - Result: 1963.49540849362
PrintResult((x, y) => (x - y).TotalDays, DateTime.Now, new DateTime(1940, 10, 9)); // Days since birth of John Lennon - Result: 25775.8028079865
' Visual Basic
PrintResult(Function(x, y) CInt(x / y), 5, 2) ' Integer division - Result: 2
PrintResult(Function(x, y) x / y, 5, 2.0) ' Real division - Result: 2.5
PrintResult(Function(x, y) 2 * y * x, 25, Math.PI) ' Circumference of a circle - Result: 157.07963267949
PrintResult(Function(x, y) y * Math.Pow(x, 2), 25, Math.PI) ' Area of circle - Result: 1963.49540849362
PrintResult(Function(x, y) (x - y).TotalDays, DateTime.Now, New DateTime(1940, 10, 9)) ' Days since birth of John Lennon - Result: 25775.8028079865

In each example, note how the types of x and y, as well as the return type of the lambda expression, are inferred by the compiler.

Delegate Re-use

Our MathFunction generic delegate can now be used as a type for any method that accepts two parameters of any type and returns a value of any type. As such, our delegate is highly re-usable and not just restricted to our simple calculator scenario.

Indeed, Microsoft have included a whole stack of generic Func and Action delegates in the System namespace which would probably cover most conceivable scenarios. As such, we could have used one of those in our example here; however that would have defeated the object of the exercise. I personally believe there is still a strong case for creating your own delegates in certain scenarios, as it often improves code readability.


Using generics allows us to write more versatile delegates which can be re-used for a whole variety of different scenarios. Even when using generic delegates, the compiler is still able to infer the types of any parameters and the return type.

Next: Event Handling

No comments:

Post a Comment