Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# closure variable scope

Tags:

c#

.net

A(nother?) question about how variable scope is applied in relation to closures. Here's a minimal example:

public class Foo
{
    public string name;
    public Foo(string name)
    {
        this.name = name;
    }
}


public class Program
{
    static Action getAction(Foo obj)
    {
        return () => Console.WriteLine(obj.name); 
    }

    static void Main(string[] args)
    {
        Foo obj1 = new Foo("x1");    
        Action a = getAction(obj1);  
        obj1 = new Foo("x2");        
        a();                         
    }
}

This prints x1. It can be explained as:

getAction returns an anonymous function which has a closure enclosing the variable obj. obj has the same reference value as obj1 but its relationship with obj1 ends there as the closure encloses only obj. In other words, whatever value obj1 takes afterwards does not affect the closure. Therefore whenever/however a gets called (eg. a gets passed to some other function) it will always print x1.

Now my questions are:

  1. Is the above explanation correct?
  2. I don't have a specific scenario in mind but what if we wanted the program to print x2 (eg. closure to enclose an outer scope)? Could it be done (or doesn't it make sense to even attempt)?
like image 545
ubi Avatar asked Sep 15 '15 07:09

ubi


1 Answers

Let's consider:

static Action getAction(Foo obj)
{
    return () => Console.WriteLine(obj.name); 
}

The closure is over the parameter obj; this obj is a reference passed by value, so if a caller does:

x = someA();
var action = getAction(x);
x = someB(); // not seen by action

then the closure is still over the original value, because the reference (not the object) is copied when passing it to getAction.

Note that if the caller changes values on the original object, this will be seen by the method:

x = someA();
var action = getAction(x);
x.name = "something else"; // seen by action

Inside the getAction method, it is basically:

var tmp = new SomeCompilerGeneratedType();
tmp.obj = obj;
return new Action(tmp.SomeCompilerGeneratedMethod);

with:

class SomeCompilerGeneratedType {
    public Foo obj;
    public void SomeCompilerGeneratedMethod() {
        Console.WriteLine(obj.name); 
    }
}
like image 182
Marc Gravell Avatar answered Sep 25 '22 01:09

Marc Gravell