Share via


Closures and Pass by Reference

What do you think the following code will do?

  1. Compile time error
  2. Run time error
  3. Work fine
    1: static void Main(string[] args)
    2: {
    3:     int x = 10;
    4:     int y = 5;
    5:     Swap(ref x, ref y);
    6: }
    7:  
    8:  
    9: static void Swap(ref int x, ref int y)
   10: {
   11:     int temp = x;
   12:     x = y;
   13:     y = temp;
   14:     Func<int> closure = () => x;
   15: }

 

If you said 1. Compile time error then you would be correct.  The resulting error message is:

Cannot use ref or out parameter 'x' inside an anonymous method, lambda expression, or query expression  

The reason for this is that although C# provides the ability to pass parameters by reference (as opposed to by value), it offers no way to assign to a variable by reference.

This ability exists in C++ :

    1: int x = 5;
    2: int & y = x;

Now y refers to the same variable location as x.

 

So, what does this all have to do with the original code?  As I described in my previous post Understanding Variable Capturing in C#, since we are using the variable x in a lambda expression (a.k.a. a closure) the C# compiler will rewrite the method so that it looks something like this:

    1: class Capture
    2: {
    3:     public int x;
    4:     public int Lambda()
    5:     {
    6:         return this.x;
    7:     }
    8: }
    9:  
   10: static void Swap(ref int x, ref int y)
   11: {
   12:     Capture capture = new Capture();
   13:     capture.x = x;
   14:     int temp = capture.x;
   15:     capture.x = y;
   16:     y = temp;
   17:     Func<int> closure = () => capture.x;
   18: }

 

Do you see the problem now?  On line 13 the ref parameter x  is being assigned to the member variable x of the Capture class.  But since you can't assign by reference this would change the semantics of the method.  The C# compiler is smart enough to know this, so it throws the error.  It won't generate code that has different meaning than what the user intended.

Comments

  • Anonymous
    April 30, 2008
    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Anonymous
    April 30, 2008
    The other problem that makes this difficult is lifetime.  Closures in C# and VB both extend the lifetime of a variable being captured.  Locals and Parameters both switch from having method lifetime semantics to heap lifetime semantics. Reference parameters throw in a bit of a twist.  Reference parameters can be visualized as pointing to the address of a variable (similar to a pointer).  References parameters can also be value types declared on the stack.  Keeping a pointer to a stack value which lives beyond the lifetime of the declaring method frame would be unsafe and hence is illegal in DotNet.  

  • Anonymous
    April 30, 2008
    "Keeping a pointer to a stack value which lives beyond the lifetime of the declaring method frame would be unsafe and hence is illegal in DotNet." This is not true. CLR does not allow to have fields of reference types, yes, but it does allow methods to return references (can't write that in C#, but you can in C++/CLI, or in raw CIL). The resulting code is unverifiable, but otherwise correct, and it does work. And once you have that, you can return a reference to your own local variable (which than consequently goes out of scope) - and then it's U.B. all the way, of course. But still, legal CIL...

  • Anonymous
    May 01, 2008
    The comment has been removed

  • Anonymous
    May 01, 2008
    Interesting, I wasnt aware that the CLR allows returning references to value on the stack (in unverifiable code).   Also, Jared, go to bed earlier you got work in the morning :)