Wednesday, January 6, 2010

Anonymous Methods


Anonymous Methods
In the original C# language, to construct a delegate object you have to provide it with the name of a method. The code below shows an example: ‘Print’ is first defined as a particular delegate type, then its instance ‘delegateVariable’ is created by passing it the name of the method ‘realMethod’.
1.
delegate void Print (string s);
2.
Print delegateVariable = new Print(realMethod);
3.
4.
public void realMethod (string myString)
5.
{
6.
    MessageBox.Show(myString);
7.
}

Now, however, C# has been updated to include ‘anonymous’ methods (which should be pretty easy for anyone who has used anonymous functions in languages like Javascript). These allow you to construct a delegate by specifying, rather than just naming a method. The following gives an example of how the above code could be written using an anonymous method:
1.
public delegate void Print (string s);
2.
Print delegateVariable = delegate(string myString) {MessageBox.Show(myString);};

In the above case, the anonymous method is given the same signature as the delegate it is passed to. But this is not always necessary. For the anonymous method to be acceptable, the following two conditions must be met:
1. Either the anonymous method has a parameter list that exactly matches the delegate’s parameters; or the anonymous method has no parameter list and the delegate has no ‘out’ parameters.
2. Either the values returned by the anonymous method are all of the right type for the delegate; or the anonymous method doesn’t return anything and the delegate’s return type is ‘void’.
An implication of the first condition is that an anonymous method with no parameters at all can fit a delegate with parameters. The following code is thus possible (notice that we’ve had to remove the use of ‘myString’ in the Show method, because we’re no longer passing it in):
1.
public delegate void Print (string s);
2.
Print delegateVariable = delegate {MessageBox.Show(“hello world!”);};

Anonymous methods can also make use of the local variables and parameters in whose scope the anonymous method lies. This is a somewhat complicated, and we don’t yet have a clear idea of when one should exploit it. So to illustrate it we’ll borrow the example from the documentation.
1.
delegate int myDelegate();
2.
class Test
3.
{
4.
    static myDelegate myFunc()
5.
    {
6.
        int x=0;
7.
        myDelegate result = delegate {return ++x;}
8.
        return result;
9.
    }
10.
    
11.
    static void Main()
12.
    {
13.
        myDelegate d = myFunc();
14.
        Console.WriteLine(d());
15.
        Console.WriteLine(d());
16.
        Console.WriteLine(d());
17.
    }
18.
}

Here the delegate ‘result’ declared in the function ‘myFunc’ makes use of (in the jargon, captures) the integer ‘x’ which is declared in the same function. Now, if we run the code, the output is this:
1
2
3
This result is somewhat surprising, since the integer ‘x’ is instantiated and its value maintained across three delegate calls after the function myFunc has finished running. How this is described in the documentation is that the lifetime of the captured outer type is ‘extended’, so that it lasts as long as any delegate that references it.
A further point to note is that in the example above each delegate is clearly referencing the same type instance. But there are situations in which local variables get initialized many times within a function, and these variables will count as different type instances. For example, in the following loop, the integer i is initialised three times, and the delegates added into the myDelegateArray will reference different variables.
1.
myDelegate[] myDelegateArray = new myDelegate[3];
2.
for (int x=0; x<3; x++)
3.
{
4.
    int i = x;
5.
    myDelegateArray[x] = delegate {return ++i};
6.
}

One question that this naturally raises is whether the multiple initialisation of i gives a performance hit, which one could avoid by declaring i outside the loop. But the documentation suggests that this isn’t the case; each new instance of i just slots neatly into the place vacated by the previous one.