Saturday, January 5, 2008

Lambdas & Closures in C# 3.0

Here's some code the exhibits the creation of a closure in C#.  This was possible with the advent of anonymous delegates in C# 2.0, but it looks a lot cleaner in C# 3.0.

using System;
using System.Linq;
using System.Collections.Generic;

public class Program
public static void Main(string[] args)
ClosuresWithLambdas a = new ClosuresWithLambdas();

List<Func<int>> fs = a.GetNumberFunctions();

Func<int> increments_i_func = fs[0];
Func<int> just_returns_i_func = fs[1];

//prints 1
//prints 2

public class ClosuresWithLambdas
public List<Func<int>> GetNumberFunctions()
//this variable gets included in the closure
int j = 1;

//we use this to return two functions
List<Func<int>> fs = new List<Func<int>>(2);

//don't get confused by the postfix operator
//the f lambda returns j BEFORE incrementing it
Func<int> f = () => j++;

//g just returns the variable j
Func<int> g = () => j;

//add the two functions to the list and return it
return fs;

public void PrintReturnValue(Func<int> f)

This closure of which I speak is not immediately evident to the untrained eye.  The basic concept in this example is that the variable "j" has to survive past the end of its normal scope, i.e. the execution of GetNumberFunctions.  It has to survive because the two functions passed out of GetNumberFunctions--the lambdas "f" and "g"--refer to "j" outside of the function in which they were defined.  The compiler detects this (perfectly legal) reference as a lexical closure; it generates a class to contain these functions and the variable "j".  Here is that generated helper class viewed from Lutz Roeder's Reflector.

private sealed class <>c__DisplayClass2
// Fields
public int j;

// Methods
public int <GetNumberFunctions>b__0()
return this.j++;

public int <GetNumberFunctions>b__1()
return this.j;

As you can probably gather b__0 is "f" and b__1 is "g" (or, "increments_i_func" and "just_returns_i_func" respectively).  They share "j" in the scope of an instance of this compiler generated sealed class instance.  There are other ways for language and compiler to implement closures, but this generated inner class works just fine.  For a practical example of using closures, please see my previous article on doing asynchronous network I/O with closures.