Search

Wednesday, 4 May 2011

Delegates 101 - Part II: Anonymous Methods and Lambdas


Introduction

In Part I of this series, we looked at what .NET delegates are, and how they can be used to hold methods in variables, or to pass methods as parameters into other methods.

In this article, we are going to look at how we can use anonymous methods and lambdas, to make our code more terse by reducing the amount of code we need to write.

Anonymous Methods

Firstly, here is a recap of our SimpleCalc.exe code from Part I:

// C#
delegate int MathFunction(int int1, int int2);

static int Add(int int1, int int2)
{
    return int1 + int2;
}

static int Subtract(int int1, int int2)
{
    return int1 - int2;
}

static int Multiply(int int1, int int2)
{
    return int1 * int2;
}

static int Divide(int int1, int int2)
{
    return int1 / int2;
}

static void PrintResult(MathFunction mathFunction, int int1, int int2)
{
    int result = mathFunction(int1, int2);
    Console.WriteLine(String.Format("Result is {0}", result));
}

static void Main(string[] args)
{
    int left = int.Parse(args[0]);
    char theOperator = args[1][0];
    int right = int.Parse(args[2]);
    MathFunction mathFunction;
    if (theOperator == '+')
        mathFunction = Add;
    else if (theOperator == '-')
        mathFunction = Subtract;
    else if (theOperator == '*')
        mathFunction = Multiply;
    else
        mathFunction = Divide;
    PrintResult(mathFunction, left, right);
}
' Visual Basic
Delegate Function MathFunction(ByVal int1 As Integer, ByVal int2 As Integer) As Integer

Function Add(ByVal int1 As Integer, ByVal int2 As Integer) As Integer
    Return int1 + int2
End Function

Function Subtract(ByVal int1 As Integer, ByVal int2 As Integer) As Integer
    Return int1 - int2
End Function

Function Multiply(ByVal int1 As Integer, ByVal int2 As Integer) As Integer
    Return int1 * int2
End Function

Function Divide(ByVal int1 As Integer, ByVal int2 As Integer) As Integer
    Return CInt(int1 / int2)
End Function

Sub PrintResult(ByVal mathFunction As MathFunction, ByVal int1 As Integer, ByVal int2 As Integer)
    Dim result As Integer = mathFunction(int1, int2)
    Console.WriteLine(String.Format("Result is {0}", result))
End Sub

Sub Main(ByVal args() As String)
    Dim left As Integer = Integer.Parse(args(0))
    Dim theOperator As Char = args(1)(0)
    Dim right As Integer = Integer.Parse(args(2))
    Dim mathFunction As MathFunction
    If theOperator = "+" Then
        mathFunction = AddressOf Add
    ElseIf theOperator = "-" Then
        mathFunction = AddressOf Subtract
    ElseIf theOperator = "*" Then
        mathFunction = AddressOf Multiply
    Else
        mathFunction = AddressOf Divide
    End If
    PrintResult(mathFunction, left, right)
End Sub

As you can see, each method that we want to assign to a variable of type MathFunction has been declared as a specific, named method (e.g.: Add(), Subtract() etc.). Now, it would be good if we could specify our MathFunction methods anonymously and in-line as we pass them into our PrintResult() method.

Well, this we can do as follows:

// C#
static void Main(string[] args)
{
    int left = int.Parse(args[0]);
    char theOperator = args[1][0];
    int right = int.Parse(args[2]);
    if (theOperator == '+')
        PrintResult(delegate(int int1, int int2) { return int1 + int2; }, left, right);
    else if (theOperator == '-')
        PrintResult(delegate(int int1, int int2) { return int1 - int2; }, left, right);
    else if (theOperator == '*')
        PrintResult(delegate(int int1, int int2) { return int1 * int2; }, left, right);
    else
        PrintResult(delegate(int int1, int int2) { return int1 / int2; }, left, right);
}

Note how the parameters required by our anonymous methods are enclosed between the parentheses; and the main body of our methods are enclosed in curly braces. This removes the need to have named methods for each of our mathematical functions, considerably reducing the number of lines of code.

If you are wondering why there is no Visual Basic example, it is because Visual Basic currently does not support anonymous methods. It does, however, support the use of Lambdas.

Lambdas

A statement lambda is, to all intents and purposes, simply a shorthand way of writing an anonymous method. We can thus shorten our code further by changing our anonymous methods into statement lambdas, as follows:

// C#
static void Main(string[] args)
{
    int left = int.Parse(args[0]);
    char theOperator = args[1][0];
    int right = int.Parse(args[2]);
    if (theOperator == '+')
        PrintResult((int1, int2) => { return int1 + int2; }, left, right);
    else if (theOperator == '-')
        PrintResult((int1, int2) => { return int1 - int2; }, left, right);
    else if (theOperator == '*')
        PrintResult((int1, int2) => { return int1 * int2; }, left, right);
    else
        PrintResult((int1, int2) => { return int1 / int2; }, left, right);
}
' Visual Basic
Sub Main(ByVal args() As String)
    Dim left As Integer = Integer.Parse(args(0))
    Dim theOperator As Char = args(1)(0)
    Dim right As Integer = Integer.Parse(args(2))
    If theOperator = "+" Then
        PrintResult(Function(int1, int2)
                        Return int1 + int2
                    End Function, left, right)
    ElseIf theOperator = "-" Then
        PrintResult(Function(int1, int2)
                        Return int1 - int2
                    End Function, left, right)
    ElseIf theOperator = "*" Then
        PrintResult(Function(int1, int2)
                        Return int1 * int2
                    End Function, left, right)
    Else
        PrintResult(Function(int1, int2)
                        Return CInt(int1 / int2)
                    End Function, left, right)
    End If
End Sub

Note how the types of our int1 and int2 parameters are now automatically inferred by the compiler.

We can now reduce our code even further by using expression lambdas instead of statement lambdas. Expression lambdas are single-line lambdas which implicitly return a value. In our example, they would look as follows:

// C#
static void Main(string[] args)
{
    int left = int.Parse(args[0]);
    char theOperator = args[1][0];
    int right = int.Parse(args[2]);
    if (theOperator == '+')
        PrintResult((int1, int2) => int1 + int2, left, right);
    else if (theOperator == '-')
        PrintResult((int1, int2) => int1 - int2, left, right);
    else if (theOperator == '*')
        PrintResult((int1, int2) => int1 * int2, left, right);
    else
        PrintResult((int1, int2) => int1 / int2, left, right);
}
' Visual Basic
Sub Main(ByVal args() As String)
    Dim left As Integer = Integer.Parse(args(0))
    Dim theOperator As Char = args(1)(0)
    Dim right As Integer = Integer.Parse(args(2))
    If theOperator = "+" Then
        PrintResult(Function(int1, int2) int1 + int2, left, right)
    ElseIf theOperator = "-" Then
        PrintResult(Function(int1, int2) int1 - int2, left, right)
    ElseIf theOperator = "*" Then
        PrintResult(Function(int1, int2) int1 * int2, left, right)
    Else
        PrintResult(Function(int1, int2) CInt(int1 / int2), left, right)
    End If
End Sub

Note how the body of the lambda is no longer enclosed within an explicit code block; and that the the explicit return statement has been removed.

Summary

Anonymous methods (C# only) and lambdas (C# and VB.NET) allow us to write much more concise code by removing the need to declare and name each method specifically.

Next: Generic Delegates

1 comment:

  1. While your article is top notch, I miss the note about the code beeing shorter but less readable. Thus Lambdas (and anonymous methods) should be used in small doses.

    Thanks for that article!

    ReplyDelete