Monday, November 29, 2010

C#: Lamda, Closure, Yield and Enumeration

Given the following code in C#:

        IEnumerable<Func<int>> EnumA()
        {
            var ret = new List<Func<int>>();
            for (var i = 0; i < 3; i++)
                ret.Add(() => i++);
            return ret;
        }


        IEnumerable<Func<int>> EnumB()
        {
            for (var i = 0; i < 3; i++)
                yield return () => i++;
        }


        void Test()
        {
            foreach (var funcA in EnumA())
                Debug.WriteLine("A" + funcA());


            foreach (var funcB in EnumB())
                Debug.WriteLine("B" + funcB());
        }


The output is:
A3A4A5B0B2

The enumerator returned by EnumA() is a List.  As a result, the lamda expression (() => i++) is not executed while EnumA() executes.  Changes the lamda expression makes to i are done after EnumA returns.  Because of closure, funcA() begins with the latest value of i, which is 3.

The enumerator returned by EnumB() is implemented using yield return.  As a result, the lamda expression (() => i++) could be executed while in EnumB(), depending on the consumer of the enumeration.  In this example, the lamda expression is executed once (the call to funcB()) while EnumB iterates.  Because of closure, funcB() shares i with EnumB(), effectively double-incrementing i.

Consider pasting the code into Visual Studio and stepping through the execution with the debugger.

These links provide more insight on closure.

http://www.codethinked.com/post/2010/06/22/C-Closures-Explained.aspx
http://diditwith.net/PermaLink,guid,235646ae-3476-4893-899d-105e4d48c25b.aspx