次の方法で共有


Closures

Closures are one of the key components in C# 3.0 that makes functional programming easy, and results in clean syntax.  Yet, they are not really necessary to understand in order to write queries in the functional style.  Why?  Because closures make the language ‘just work’ exactly in the way that you expect.  I’ve made use of closures literally hundreds of times in the code that I’ve posted on my blog, yet I’ve never had to call attention to them.  This blog post presents a quick and easy explanation of what closures are, and how they work.

This blog is inactive.
New blog: EricWhite.com/blog

Blog TOCI’ve seen explanations of closures that make use of edge cases to demonstrate them, or use strange code that contains side-effects to show something about them.  But it’s not necessary.  You can see the necessity of closures in the simplest of LINQ code.  Here is an example that demonstrates a closure:

static IEnumerable<int> F(int[] source, int x)
{
return source.Where(i => i < x);
}

static void Main(string[] args)
{
int[] myArray = new[] { 3, 5, 2, 8, 1, 7, 6, 9 };
var q = F(myArray, 5);
foreach (var i in q)
Console.WriteLine(i);
}

This is a very simple LINQ program.  We have a method F that takes an integer argument x, and uses the integer argument in a predicate (conditional expression) that is passed to the Where extension method.  And as you’d expect, this program prints the integers in myArray that are less than five.

So where’s the closure?  Well, that integer argument x is actually a local variable.  That’s what parameters are – a special case of local variables.  Normally, parameters live on the stack.  And because of deferred execution, the query in method F is not iterated until the program iterates through q in Main.  Yet, when iterating through q, the program has already returned from method F.  Normally, x would no longer be in scope, and when iterating through the query, the code would be referring to a non-existent local variable.

So how does this work?  The compiler sees that the lambda expression that is passed to Where references x.  So the compiler boxes x, and puts it on the heap.  In this case, under the covers, x isn’t really a value type; it’s a reference type.  And because it’s a reference type, and the query that is stored in the variable q refers to it, it will not be eligible for garbage collection until q is eligible.

To make this even clearer, I’ll modify the example.  In this next example, I’ll write a small iterator method LessThan, which yield returns a collection where every item in the result collection is less than the argument passed in.  To make it patently clear, the method assigns the parameter to a local variable z, and then uses z to select the items in the source array.

Further, we’ll call it twice, passing two different values, 5 and 8.

public static IEnumerable<int> LessThan(IEnumerable<int> source, int x)
{
int z = x;
Console.WriteLine("Yield returning values less than {0}", z);
foreach (var item in source)
if (item < z)
yield return item;
}

static void Main(string[] args)
{
int[] myArray = new[] { 3, 5, 2, 8, 1, 7, 6, 9 };
Console.WriteLine("Before setting up the query");
IEnumerable<int> q1 = LessThan(myArray, 5);
IEnumerable<int> q2 = LessThan(myArray, 8);
Console.WriteLine("Before iterating");
foreach (var i in q1)
Console.WriteLine(i);
Console.WriteLine("===");
foreach (var i in q2)
Console.WriteLine(i);
}

When run, this example produces this output:

Before setting up the query
Before iterating
Yield returning values less than 5
3
2
1
===
Yield returning values less than 8
3
5
2
1
7
6

Due to lazy evaluation, as expected, we see the messages “Before setting up the query” and “Before iterating” before we see the line written in the LessThan method.  And we can see, for each respective query, the iterator block has captured the value in the local variable.  The variable z is really on the heap.  And there are two instances of it - one for each call into LessThan.

So, closures are just there, operating behind the scenes, making life easy for us when writing queries in the functional style.  You don’t need complicated examples to see their use.  When we write anything more complicated than the very simplest queries, we probably will make use of closures, and we don’t really have to think about it.

Note that this behind-the-scenes boxing of value types doesn’t change the semantics of the language, as far as we, as programmers, are concerned.  We still have to write code as though those captured values are value types.

For more information about yield return, see The Yield Contextual Keyword.

For more information on lazy evaluation, see Lazy Evaluation (And in Contrast, Eager Evaluation).

For a more comprehensive overview of functional programming in C# 3.0, see Query Composition Using Functional Programming Techniques in C# 3.0.

Comments

  • Anonymous
    September 11, 2008
    PingBack from http://www.easycoded.com/closures/

  • Anonymous
    September 14, 2008
    .NETClosuresAlgorithmtoDetectBlankImagesPerformanceAnalysisRevealsChar[]ArrayisBette...

  • Anonymous
    September 14, 2008
    .NET Closures Algorithm to Detect Blank Images Performance Analysis Reveals Char[] Array is Better than