FREE BOOK

Chapter 12 - Delegates and Lambda Expressions

Posted by Addison Wesley Free Book | LINQ October 13, 2009
C# achieves the same functionality using a delegate, which encapsulates methods as objects, enabling an indirect method call bound at runtime.

Lambda Expression and Anonymous Method Internals

Lambda expressions (and anonymous methods) are not an intrinsic construct within the CLR. Rather, the C# compiler generates the implementation at compile time. Lambda expressions provide a language construct for an inline-declared delegate pattern. The C# compiler, therefore, generates the implementation code for this pattern so that the compiler automatically writes the code instead of the developer writing it manually. Given the earlier listings, therefore, the C# compiler generates CIL code that is similar to the C# code shown in Listing 12.19.

Listing 12.19: C# Equivalent of CIL Generated by the Compiler for Lambda Expressions

class DelegateSample
{
// ...
static void Main(string[] args)
{
int i;
int[] items = new int[5];
for (i=0; i<items.Length; i++)
{
Console.Write("Enter an integer:");
items[i] = int.Parse(Console.ReadLine());
}
for (i = 0; i < items.Length; i++)
{
Console.WriteLine(items[i]);
}
}
}
BubbleSort(items,
DelegateSample.__AnonymousMethod_00000000);
private static bool __AnonymousMethod_00000000(
int first, int second)
{
return first < second;
}

Outer Variables

Local variables (including parameters) declared outside an anonymous function (such as a lambda expression), but captured (accessed) within the lambda expression, are outer variables of that anonymous function. this is also an outer variable.Outer variables captured by anonymous functions live on until after the anonymous function's delegate is destroyed. In Listing 12.20, it is relatively trivial to use an outer variable to count how many times swap is called by BubbleSort(). Output 12.2 shows the results of this listing.

Listing 12.20: Using an Outer Variable in a Lambda Expression

class DelegateSample
{
// ...
static void Main(string[] args)
{
int i;
int[] items = new int[5];
for (i=0; i<items.Length; i++)
{
Console.Write("Enter an integer:");
items[i] = int.Parse(Console.ReadLine());
}
for (i = 0; i < items.Length; i++)
{
Console.WriteLine(items[i]);
}
}
}
int swapCount=0;
BubbleSort(items,
(int first, int second) =>
{
bool swap = first < second;
if(swap)
{
swapCount++;
}
return swap;
}
);
Console.WriteLine("Items were swapped {0} times.",
swapCount);


swapCount appears outside the lambda expression and is incremented inside it. After calling the BubbleSort() method, swapCount is printed out to the console.

As this code demonstrates, the C# compiler takes care of generating CIL code that shares swapCount between the anonymous method and the call site, even though there is no parameter to pass swapCount within the anonymous delegate, nor within the BubbleSort() method. Given the sharing of the variable, it will not be garbage-collected until after the delegate that references it goes out of scope.

Outer Variable Internals

The CIL code generated by the C# compiler for outer variables is more complex than the code for a simple anonymous method, because the outer variable must be captured in a thread-safe manner. Listing 12.21 shows the C# equivalent of the CIL code used to implement outer variables.

Listing 12.21: C# Equivalent of CIL Code Generated by Compiler for Outer Variables

class DelegateSample
{
// ...

OUTPUT 12.2:

Enter an integer:5
Enter an integer:1
Enter an integer:4
Enter an integer:2
Enter an integer:3
5
4
3
2
1

Items were swapped 4 times.

    private sealed class __LocalsDisplayClass_00000001
{
public int swapCount;
public bool __AnonymousMethod_00000000(
int first, int second)

static void Main(string[] args)
{
int i;
int[] items = new int[5];
for (i=0; i<items.Length; i++)
{
Console.Write("Enter an integer:");
items[i] = int.Parse(Console.ReadLine());
}
for (i = 0; i < items.Length; i++)
{
Console.WriteLine(items[i]);
}
}
}

Notice that the captured local variable is never "passed" anywhere and is never "copied" anywhere. Rather, the captured local variable (swapcount) is a single variable whose lifetime we have extended by implementing it as an instance field rather than as a local. All references to the local variable are rewritten to be references to the field.

Total Pages : 9 56789

comments