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];
    }
}