Introduction
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 math theme, let's look again at our original MathFunction
delegate:
delegate int MathFunction(int int1, int int2);
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 using a generic delegate, we can have a single delegate which will handle any data type we like in a type-safe manner:
delegate TResult MathFunction<T1, T2, TResult>(T1 var1, T2 var2);
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:
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));
}
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:
PrintResult((x, y) => x / y, 5, 2);
PrintResult((x, y) => x / y, 5, 2.0);
PrintResult((x, y) => 2 * y * x, 25, Math.PI);
PrintResult((x, y) => y * Math.Pow(x, 2), 25, Math.PI);
PrintResult((x, y) => (x - y).TotalDays,
DateTime.Now, new DateTime(1940, 10, 9));
PrintResult(Function(x, y) CInt(x / y), 5, 2)
PrintResult(Function(x, y) x / y, 5, 2.0)
PrintResult(Function(x, y) 2 * y * x, 25, Math.PI)
PrintResult(Function(x, y) y * Math.Pow(x, 2), 25, Math.PI)
PrintResult(Function(x, y) (x - y).TotalDays, _
DateTime.Now, New DateTime(1940, 10, 9))
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 has 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.
Summary
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 type of any parameter and the return type.
Next: Event Handling.