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
现在我的问题是:
- 以上解释正确吗?
- 我没有一个具体的场景,但如果我们想让程序打印
x2
(例如:闭包包含外部作用域)?这能做到吗(或者尝试一下都没有意义)?
让我们考虑:
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 obj1
是Main
和 中的变量 -
Foo obj
是getAction
中的变量。
当你选择将另一个对象(或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节),而是形参所属的对象用调用中给出的参数值初始化。值形参在函数成员返回时停止存在。为了进行定赋值检查,值形参为