C#闭包变量范围

时间:2015-09-15 07:21:18

标签: c# .net

关于如何应用与闭包相关的变量范围的问题(另一个?)。这是一个最小的例子:

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();                         
    }
}

这会打印x1。它可以解释为:

getAction返回一个匿名函数,该函数有一个封闭变量obj的闭包。 obj具有与obj1相同的引用,但它与obj1的关系在那里结束,因为闭包只包含obj。换句话说,之后的任何值obj1都不会影响闭包。因此,只要a被调用(例如。a被传递给其他函数),它就会始终打印x1

现在我的问题是:

  1. 上述说明是否正确?
  2. 我没有考虑具体的情况但是如果我们希望程序打印x2(例如封闭以封闭外部范围)会怎么样?可以这样做(或者甚至尝试都没有意义吗?)

5 个答案:

答案 0 :(得分:11)

让我们考虑一下:

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

闭包位于参数 static Action getAction(Foo obj) { return () => Console.WriteLine(obj.name); } 之上;此obj是按值传递的引用,因此如果调用方执行:

obj

然后闭包仍然在原始值之上,因为在将引用(而不是对象)传递给x = someA(); var action = getAction(x); x = someB(); // not seen by action 已复制

请注意,如果调用者更改原始对象的值,则方法可以看到:

getAction

x = someA(); var action = getAction(x); x.name = "something else"; // seen by action 方法中,基本上是:

getAction

使用:

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

答案 1 :(得分:3)

简短回答:解释是正确的,如果您想将值从x1更改为x2,则必须更改传递给操作的特定对象。

obj1.name = 'x2'

当一个对象作为参数传递给一个函数时,它将引用(指针)复制到obj。

那时你有一个对象和两个引用;

  • Foo obj1
  • 中的变量Main
  • Foo obj
  • 中的变量getAction

每当您选择将另一个对象(或null)设置为obj1时,它不会影响getAction中的第二个引用。

答案 2 :(得分:3)

以下是为Main生成的IL:

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      

我从中推断出当你调用getAction()时,它会为obj1创建一个带有值的方法,当你创建一个新实例并调用该委托时,由于它已经关闭了编译器创建的方法中的值,因为它打印 x1

当你打电话给getAction(obj1)时,Foo obj现在指的是新的Foo(“X1”)``,然后你正在做obj1 = new Foo("x2"),现在obj1已经new Foo("x2")的引用但Foo obj getAction(Foo obj)的引用仍引用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")

答案 3 :(得分:1)

您可以将代码重写为

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();                         
    }
}

这就是内部发生的事情。您将引用分配给obj并构建引用obj的操作。

答案 4 :(得分:1)

你的解释是正确的,基本上是一种改写Section 5.1.4中C#语言规范中所写内容的方式(此处为完整性而强调,我的重点):

  

没有ref或out修饰符声明的参数是一个值   参数。

     

值参数在调用时出现   函数成员(方法,实例构造函数,访问器或   运算符)(参数7.4)参数所属,是   使用调用中给出的参数值初始化。 A.   返回函数成员时,value参数不再存在。

     

为了明确赋值检查,值参数是   考虑最初被分配。