C# 如果当地人';在它们超出范围后被调用的匿名函数重新引用?
考虑这个人为的例子:C# 如果当地人';在它们超出范围后被调用的匿名函数重新引用?,c#,scope,anonymous-function,C#,Scope,Anonymous Function,考虑这个人为的例子: public static class Test { private static List<Action> actions = new List<Action>(); private static Int32 _foo = 123; public static void Foo() { Int32 foo = _foo += 123; Object bar = ne
public static class Test {
private static List<Action> actions = new List<Action>();
private static Int32 _foo = 123;
public static void Foo() {
Int32 foo = _foo += 123;
Object bar = new Object();
IDisposable baz = GetExpensiveObject();
Action callback = new Action(delegate() {
DoSomething( foo, bar, baz );
baz.Dispose();
});
foo = 456;
bar = new Object();
actions.Add( callback );
}
public static void Main() {
Foo();
Foo();
foreach(Action a in actions) a();
}
}
公共静态类测试{
私有静态列表操作=新建列表();
私有静态Int32_foo=123;
公共静态void Foo(){
Int32 foo=_foo+=123;
对象栏=新对象();
IDisposable baz=GetExpensiveObject();
动作回调=新动作(委托(){
DoSomething(foo、bar、baz);
baz.Dispose();
});
foo=456;
bar=新对象();
actions.Add(回调);
}
公共静态void Main(){
Foo();
Foo();
foreach(动作中的动作a)a();
}
}
查看Main
,假设调用了两次Foo
,然后执行操作的内容(现在有2个Action
实例),那么回调中变量Foo
、bar
和baz
的状态是什么
如果从未调用callback
,是否会处理baz
(因为actions
中包含callback
中的引用),以及调用了什么actions.Clear()
,是否会处理baz
(我不是在一台有编译器或IDE的计算机上进行测试)好吧,提醒一下,如果在那里使用匿名方法,局部变量的生存期将延长到匿名方法的生存期。这并不意味着在创建匿名方法时复制变量的值。所以每次使用“456”和第二个创建的对象调用“DoSomething”
如果您创建了一个新的WinForms项目,在表单上放置一个新按钮并添加以下代码,则可以进行检查:
private void Form1_Load(object sender, EventArgs e)
{
int i = 123;
this.button1.Click += (Lsender, Le) => { MessageBox.Show(i.ToString()); };
i = 456;
}
请注意这里的引用类型,因为如果您要编写
{
private static Foo(object value)
{
object bar = value;
//...
}
private static void Main()
{
object obj = new object();
Foo(obj);
Foo(obj);
//...
}
}
在这种情况下,每个回调都有自己的变量“bar”,但每个回调都引用堆内存中的同一对象。编译器将重写匿名方法,以保留对堆上与本地作用域相同的内存区域的引用。垃圾收集器将发现此引用处于活动状态,并且在匿名方法也被收集之前不会对目标进行垃圾收集
然而。。。如果不是在堆上分配,而是在可能被新方法调用覆盖的堆栈上分配,该怎么办?;)
private static void Main(字符串[]args){
var rng=CreateRNG();
Console.WriteLine(rng());
Console.WriteLine(rng());
Console.ReadLine();
}
私有静态不安全函数CreateRNG(){
var v=stackalloc Int32[1];
v[0]=4;
return()=>v[0];
}
这段代码第一次调用打印4,第二次打印半随机数
真正的代码,使用Reflector提取并手动清理,方法重命名以便编译(编译器使用特殊字符,如自动生成的方法名称):
private static unsafe Func CreateRNG(){
Int32*numPtr=stackalloc Int32[1];
var class2=新的_c__displayclas1();
类别2.v=numPtr;
类别2.v[0]=4;
返回新函数(类2.\u CreateRNG\u b\u 0);
}
[编译生成]
公共密封类\uuuu c\uuuu显示类1{
公共不安全Int32*v;
公共不安全Int32\u CreateRNG\u b\u 0(){
返回v[0];
}
}
这表明编译器将匿名方法重写为一个新函数,在本例中是在一个新类中,以保存任何引用的局部值。如果不需要保留本地引用,则不需要该类
我还可以猜到,第一次调用是有效的,因为我们调用了返回的Func
,它很容易读取值。方法体非常小,可能是内联的。值4被传递到Console.WriteLine
,该方法调用可能正在覆盖堆栈(或者Console.WriteLine
依次覆盖的方法调用),从而更改指针指向的值。您所在的计算机可以访问internet,因此您可以访问IDE:
private static void Main(String[] args) {
var rng = CreateRNG();
Console.WriteLine(rng());
Console.WriteLine(rng());
Console.ReadLine();
}
private static unsafe Func<Int32> CreateRNG() {
var v = stackalloc Int32[1];
v[0] = 4;
return () => v[0];
}
private static unsafe Func<Int32> CreateRNG() {
Int32* numPtr = stackalloc Int32[1];
var class2 = new __c__DisplayClass1();
class2.v = numPtr;
class2.v[0] = 4;
return new Func<Int32>(class2._CreateRNG_b__0);
}
[CompilerGenerated]
public sealed class __c__DisplayClass1 {
public unsafe Int32* v;
public unsafe Int32 _CreateRNG_b__0() {
return v[0];
}
}