Closures in C#: Variable Scoping and Value Types vs Reference Types


I read Sergio’s post on “Javascript, time to grok closures” today and it was very enlightening. Overall, it helped me to understand closures more than I previously had – not just in Javascript, though. I put together a quick sample on closures in C# to illustrate the same behavior that Sergio is talking about in the ‘Closures can be tricky’ section of his post. I’m happy to see C# is behaving the same way as Javascript, in the case of ‘value’ types in closures. This probably isn’t new to anyone that understands closures already. It’s new to me, though, and seems to be a fairly important concept to understand when using anonymous methods (lambda expression or otherwise) and closures.

Illustrating The Closure Scope For Value Types

Here is the sample code I put together, in unit test form, to illustrate the same behavior that Sergio is pointing out.

[TestFixture]
public class When_referencing_a_value_type_variable_from_the_parent_scope_directly_in_a_closure : ContextSpecification
{
   private readonly IList<Func<int>> theActions = new List<Func<int>>();
 
   protected override void Context()
   {
       for (int i = 0; i <= 3; i++)
       {
           Func<int> foo = (() => { return i; });
           theActions.Add(foo);
       }
   }
 
   [Test]
   public void Should_reference_the_original_value_in_the_parent_scope()
   {
       //since the value of "i" in the original scope is now 4 (having gone through all of the loop's iterations)
       //every value in the execution of the "foo" anonymous function will be 4
 
       int expectedValue = 4;
       
       int value0 = theActions[0]();
       Assert.AreEqual(expectedValue, value0);
 
       int value1 = theActions[1]();
       Assert.AreEqual(expectedValue, value1);
 
       int value2 = theActions[2]();
       Assert.AreEqual(expectedValue, value2);
 
       int value3 = theActions[3]();
       Assert.AreEqual(expectedValue, value3);
   }
 
}
 
[TestFixture]
public class When_passing_a_value_type_variable_from_the_parent_scope_to_a_method_called_by_a_closure : ContextSpecification
{
   private readonly IList<Func<int>> theActions = new List<Func<int>>();
 
   private void dooFoo(int value)
   {
       Func<int> foo = (() => { return value; });
       theActions.Add(foo);
   }
 
   protected override void Context()
   {
       for (int i = 0; i <= 3; i++)
       {
           dooFoo(i);
       }
   }
 
   [Test]
   public void Should_create_a_copy_of_the_variable_and_retain_the_original_value()
   {
       //since we called a method, passing "i" from the original context into that method,
       //the copied value was sent to the "foo" anonymous function, meaning that we got all
       //of the numbers - 0, 1, 2, and 3 - in the anonymous "foo" functions.
       
       int expectedValue = 0;
       int value0 = theActions[0]();
       Assert.AreEqual(expectedValue, value0);
 
       expectedValue = 1;
       int value1 = theActions[1]();
       Assert.AreEqual(expectedValue, value1);
 
       expectedValue = 2;
       int value2 = theActions[2]();
       Assert.AreEqual(expectedValue, value2);
 
       expectedValue = 3;
       int value3 = theActions[3]();
       Assert.AreEqual(expectedValue, value3);
   }
 
}
</p>

Illustrating The Closure Scope For Reference Types

It gets a little more interesting when dealing with reference types. The same basic statements can be made for reference types, but we have to remember that we’re now dealing with what are essentially pointers, not values. So, when a reference type variable gets copied, it still points to the same object on the heap. Thus, when dealing with reference types in closures, the scope of the variable that creates the reference type suddenly becomes much more important.

To illustrate the important of variable scope when dealing with closures, I’ve created some examples that scope a small reference type (class) in a few different ways. I’ll use this class definition as the reference type in all three examples.

public class SomeClass
{
}
</p>

Closure Of A Reference In Parent Scope

[TestFixture]
public class When_creating_a_closure_for_a_reference_that_is_part_of_the_parent_scope : ContextSpecification
{
 
   SomeClass someClass;
 
   private readonly IList<SomeClass> theValues = new List<SomeClass>();
   private readonly IList<Func<SomeClass>> theClosureValues = new List<Func<SomeClass>>();
 
   protected override void Context()
   {
       for (int i = 0; i <= 3; i++)
       {
           someClass = new SomeClass();
           theValues.Add(someClass);
           Func<SomeClass> foo = (() => { return someClass; });
           theClosureValues.Add(foo);
       }
   }
 
   [Test]
   public void Then_all_closures_should_reference_the_last_instance_of_the_variable_from_the_parent_scope()
   {
       SomeClass expectedValue0 = theValues[0];
       SomeClass value0 = theClosureValues[0]();
       Assert.AreNotSame(expectedValue0, value0);
 
       SomeClass expectedValue1 = theValues[1];
       SomeClass value1 = theClosureValues[1]();
       Assert.AreNotSame(expectedValue1, value1);
 
       SomeClass expectedValue2 = theValues[2];
       SomeClass value2 = theClosureValues[2]();
       Assert.AreNotSame(expectedValue2, value2);
 
       SomeClass expectedValue3 = theValues[3];
       SomeClass value3 = theClosureValues[3]();
       Assert.AreSame(expectedValue3, value3);
   }
 
}
</p>

In this example, we’ve declared the “someClass” variable outside of the for-loop. This creates a variable that is scoped outside of the loop, and in this case outside of the Context() method. As long as this variable is scoped outside of the for-loop, we will only have a single variable to create our closure on. We see that the foo() anonymous method encloses someClass, causing it to stay in scope. What we don’t see, though, is that the foo() anonymous method will not be evaluated until the observations (just before the asserts) are executed. This means that every foo() method will have a reference to the same instance of “SomeClass”, not the individual instance that was created inside of the for-loop.

Closure Of A Reference That Is Scoped Within The Loop

[TestFixture]
public class When_creating_a_closure_for_a_reference_that_is_scoped_within_the_forloop: ContextSpecification
{
 
   private readonly IList<SomeClass> theValues = new List<SomeClass>();
   private readonly IList<Func<SomeClass>> theClosureValues = new List<Func<SomeClass>>();
 
   protected override void Context()
   {
       for (int i = 0; i <= 3; i++)
       {
           SomeClass someClass = new SomeClass();
           theValues.Add(someClass);
           Func<SomeClass> foo = (() => { return someClass; });
           theClosureValues.Add(foo);
       }
   }
 
   [Test]
   public void Then_all_closures_should_have_their_own_reference_from_the_loop_iteration()
   {
       SomeClass expectedValue0 = theValues[0];
       SomeClass value0 = theClosureValues[0]();
       Assert.AreSame(expectedValue0, value0);
 
       SomeClass expectedValue1 = theValues[1];
       SomeClass value1 = theClosureValues[1]();
       Assert.AreSame(expectedValue1, value1);
 
       SomeClass expectedValue2 = theValues[2];
       SomeClass value2 = theClosureValues[2]();
       Assert.AreSame(expectedValue2, value2);
 
       SomeClass expectedValue3 = theValues[3];
       SomeClass value3 = theClosureValues[3]();
       Assert.AreSame(expectedValue3, value3);
   }
 
}
</p>

In this example, we’ve declared the “someClass” variable inside of the for-loop, and is thus, in the local scope of the for-loop. Since the foo() anonymous method is creating a closure on a variable that is scoped to the for-loop, we can be assured that each foo() method will point to the “SomeClass” instance that was created in the for-loop. Our closure’s scope is now limited to the for-loop.

Closure Of A Reference Pointer Copy

[TestFixture]
public class When_creating_a_closure_for_a_copy_of_a_reference_pointer : ContextSpecification
{
 
   SomeClass someClass;
   private readonly IList<SomeClass> theValues = new List<SomeClass>();
   private readonly IList<Func<SomeClass>> theClosureValues = new List<Func<SomeClass>>();
 
   private void dooFoo(SomeClass value)
   {
       Func<SomeClass> foo = (() => { return value; });
       theClosureValues.Add(foo);
   }
 
   protected override void Context()
   {
       for (int i = 0; i <= 3; i++)
       {
           someClass = new SomeClass();
           theValues.Add(someClass);
           dooFoo(someClass);
       }
   }
 
   [Test]
   public void Then_all_closures_should_have_their_own_reference_to_the_copied_reference_pointer()
   {
       SomeClass expectedValue0 = theValues[0];
       SomeClass value0 = theClosureValues[0]();
       Assert.AreSame(expectedValue0, value0);
 
       SomeClass expectedValue1 = theValues[1];
       SomeClass value1 = theClosureValues[1]();
       Assert.AreSame(expectedValue1, value1);
 
       SomeClass expectedValue2 = theValues[2];
       SomeClass value2 = theClosureValues[2]();
       Assert.AreSame(expectedValue2, value2);
 
       SomeClass expectedValue3 = theValues[3];
       SomeClass value3 = theClosureValues[3]();
       Assert.AreSame(expectedValue3, value3);
   }
 
}
</p>

In this last example, we can see that the “someClass” variable is again scoped outside of the Context() method. However, notice that we are now calling a “dooFoo()” method inside of the loop, instead of creating the closure locally. The effectively means that we are not creating a closure of “someClass” anymore. When someClass is passed into the dooFoo method, a copy of the reference is made – that is, we have a new variable with a new scope; this new variable just happens to be pointing to the same object as someClass, on the heap. Since we are now creating a closure around the “value” parameter of dooFoo, the scope of the closure has changed and acts the same as the closure of a reference in that is scoped with the loop. Each foo() anonymous method, with it’s closure, will now have it’s own instance of “value” to reference – the reference that was created in the scope of the for-loop, and then copied into the “value” parameter.

###

In “Closing” (pun intended 🙂 )

It’s important to keep the scope of the variables in our closures in mind. If we scope our closures incorrectly, we can have some strange bugs in our system. Understanding the scope of your closures does require us to know a little bit of computer science, though – stack vs. heap, reference vs. value type – but that’s not a bad thing.

(If you see something wrong in my explanations or think my spec/observation names should be changed, let me know. I tried to make it clear, but am not sure that I did a good job.)

“One Team, One Aim”. It’s All About The Journey, Not The Goal