关于如何应用与闭包相关的变量范围的问题(另一个?)。这是一个最小的例子:
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
(例如封闭以封闭外部范围)会怎么样?可以这样做(或者甚至尝试都没有意义吗?)答案 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参数不再存在。
为了明确赋值检查,值参数是 考虑最初被分配。