c#闭包变量作用域

本文关键字:作用域 变量 闭包 | 更新日期: 2023-09-27 18:12:42

关于变量作用域如何应用于闭包的另一个问题。下面是一个简单的例子:

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(例如:闭包包含外部作用域)?这能做到吗(或者尝试一下都没有意义)?

c#闭包变量作用域

让我们考虑:

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

:

class SomeCompilerGeneratedType {
    public Foo obj;
    public void SomeCompilerGeneratedMethod() {
        Console.WriteLine(obj.name); 
    }
}

简短的回答:解释是正确的,如果你想把值从x1更改为x2,那么你必须更改传递给动作的特定对象。

obj1.name = 'x2'

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

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

  • Foo obj1Main
  • 中的变量
  • Foo objgetAction
  • 中的变量。

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

下面是为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")的引用,但getAction(Foo obj)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")

您可以将代码重写为

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的操作。

你的解释是正确的,基本上是对5.1.4节c#语言规范中所写内容的一种改写方式(此处报告是为了完整性,强调我的):

没有ref out修饰符的形参是一个值参数。

的调用时产生一个值形参函数成员(方法)、实例构造函数、访问器或operator)(第7.4节),而是形参所属的对象用调用中给出的参数值初始化。值形参在函数成员返回时停止存在。

为了进行定赋值检查,值形参为