Reputation: 4399
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:
x2
(eg. closure to enclose an outer scope)? Could it be done (or doesn't it make sense to even attempt)?Upvotes: 11
Views: 4510
Reputation: 22979
You explaination is correct and is basically a way of rephrasing what is written in the C# Language Specification in Section 5.1.4 (reported here for completeness, emphasis mine):
A parameter declared without a ref or out modifier is a value parameter.
A value parameter comes into existence upon invocation of the function member (method, instance constructor, accessor, or operator) (Section 7.4) to which the parameter belongs, and is initialized with the value of the argument given in the invocation. A value parameter ceases to exist upon return of the function member.
For the purpose of definite assignment checking, a value parameter is considered initially assigned.
Upvotes: 1
Reputation: 62488
Here is the IL generated for Main:
IL_0000: ldstr "x1"
IL_0005: newobj UserQuery+Foo..ctor
IL_000A: stloc.0 // obj1
IL_000B: ldloc.0 // obj1
IL_000C: call UserQuery.getAction
IL_0011: stloc.1 // a
IL_0012: ldstr "x2"
IL_0017: newobj UserQuery+Foo..ctor
IL_001C: stloc.0 // obj1
IL_001D: ldloc.1 // a
IL_001E: callvirt System.Action.Invoke
IL_0023: ret
from which i deduce that when you call getAction()
, it creates method with values in it for obj1
, when you are are creating a new instance and invoking the delegate, due to closure it has previous value in its compiler created method, due to which it prints x1
When you are calling getAction(obj1)
, Foo obj
is now referring to new Foo("X1")`` , then you are doing obj1 = new Foo("x2")
, now obj1
is has reference of new Foo("x2")
but Foo obj
of getAction(Foo obj)
is still referencing to new Foo("x1")
obj1 = new Foo("x1") // obj1 is referencing to ----> Foo("x1") memory location
getAction(obj1) // --> getAction(Foo obj) obj is referencing to Foo("x1")
obj1 = new Foo("x2") // obj1 is now referencing to----> Foo("x2") memory location
// but in getAction(Foo obj) obj is still referencing to Foo("x1")
Upvotes: 3
Reputation: 3955
Short answer: The explanation is correct and if you want to change the value from x1
to x2
then you have to change the specific object that is passed to the action.
obj1.name = 'x2'
When an object is passed to a function as parameter it copies the reference (pointer) to the obj.
At that time you have one object and two references;
Foo obj1
that is the variable in the Main
andFoo obj
that is the variable in the getAction
Whenever you choose to set another object(or null) to obj1
it will not affect the second reference in the getAction
.
Upvotes: 3
Reputation: 19106
You can rewrite your code to
public class Program
{
static void Main(string[] args)
{
Foo obj1 = new Foo("x1");
// rewrite of
// Action a = GetAction( obj1 );
Foo obj = obj1;
Action a = () => Console.WriteLine( obj.name );
obj1 = new Foo("x2");
a();
}
}
That is what internally happens. You assign the reference to obj
and build an action that refers to obj
.
Upvotes: 1
Reputation: 1062780
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);
}
}
Upvotes: 13