2013-04-03 50 views
2

考慮這個人爲的例子:在Main如果當地人被超出範圍之後被調用的匿名函數引用,會發生什麼?

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

來看,假設Foo被調用了兩次,和actions內容(現在,2個Action實例)之後執行的,什麼是變量的狀態foobarbazcallback之內?

如果callback不會被調用,將baz永遠處置(由於包含在actions包含在callback參考?),什麼是actions.Clear()被調用時,將baz處置的呢?

(我不是在對我進行測試編譯器或IDE計算機)

+0

您位於可上網的電腦上,因此您可以訪問IDE:http://ideone.com/ – Henrik

回答

1

好,提醒局部變量的壽命將延長爲匿名方法的一生,如果他們使用的有。這並不意味着變量的值在創建匿名方法時被複制。因此,每次都會使用「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); 

    //... 
} 

}

在這種情況下,每次回調都會有自己的變量「酒吧」,但他們每個人都是指的同一個對象在堆內存。

1

編譯器將重寫匿名方法,以保存對作爲本地作用域的堆上的同一內存區域的引用。垃圾收集器會找到這個引用是活動的,並且不會垃圾收集目標,直到收集到匿名方法。

但是......如果你不是在堆上分配,而是在可能被新方法調用覆蓋的堆棧上呢? ;)

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

此代碼打印4爲第一次調用,第二次打印一個半隨機數。

真正的代碼,使用反射提取並用手清理,並用重命名,因此它們將編譯方法(編譯器自動生成的方法名稱使用特殊字符,如<>):

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

由此可見編譯器會將匿名方法重寫爲一個新函數,在這種情況下,這個新類將保存任何引用的本地值。如果不需要保存本地引用,則不需要該類。

而且我還可以猜測第一個調用的工作原理是因爲我們調用返回的Func<Int32>並且它很容易讀取值。方法體非常小,可能內聯。值4傳遞給Console.WriteLine,並且該方法調用可能會覆蓋堆棧(或調用Console.WriteLine進行的方法調用),從而更改指針指向的值。

相關問題