11

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)?
5
  • Your explanation seems obvious because you have two different variables that initially refer to the same object but then you assign a different object to one of them, which has no effect on the other. I'm guessing that declaring your obj parameter as ref might address your second question, but I'm not 100% sure of that. Commented Sep 15, 2015 at 7:28
  • @jmcilhinney It is not allowed to capture ref parameter in closure. Commented Sep 15, 2015 at 7:29
  • @PetSerAl, I guess that might be to explicitly avoid the situation described here, or at least avoid the appearance that it might be possible. Commented Sep 15, 2015 at 7:35
  • @jmcilhinney IIRC, at the IL level a Foo* / ref Foo field (reference-to-a-reference, not a pointer) would be fine and understood perfectly - but that isn't a concept that can be expressed in C# (hence why I had to approximate syntax to even discuss it!), so it is perhaps reasonable that the closure has the same feature limits as hand-coding; Commented Sep 15, 2015 at 8:04
  • 1
    You've got some good answers so I won't add another. I will note that you are probably slightly misusing the word "scope". The scope of a named entity is defined as the region of program text in which that entity may be used by name without qualification. For example, method Main may not refer to obj because the scope of obj does not include method Main. But Main can use Foo because the scope of a public class does include the body of Main in another class in the same namespace. Commented Sep 15, 2015 at 14:34

5 Answers 5

13

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); } } 
Sign up to request clarification or add additional context in comments.

3 Comments

I guess you mean tmp.SomeCompilerGeneratedMethod. Not obj.SomeCompilerGeneratedMethod
Thanks.. this confirmed my understanding of how capturing works here. My second question is perhaps the most important - how can one 'expand' the closure scope eg. if one wanted to have obj1 included in the closure scope how could it be done? I assume this could be done if we could pass the reference variable as a ref but this doesn't seem to be allowed.
@ubi you would need to pass in a reference to an object that has a property that is the Foo, then you can change the outer parent.Foo, and since the lambda is now capturing the parent reference (via the parameter), any change to members of parent will be visible to the lambda
3

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;

  • The Foo obj1 that is the variable in the Main and
  • The Foo 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.

Comments

3

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") 

Comments

1

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.

Comments

1

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.