2009-08-14 77 views
5

比方說,我有以下代碼:匿名方法,範圍和序列化

public class Foo 
{ 
    private int x; 
    private int y; 

    public Bar CreateBar() 
    { 
     return new Bar(x,() => y); 
    } 
} 

[Serializable] 
public class Bar 
{ 
    private int a; 
    private Func<int> b; 

    public Bar(int a, Func<int> b) 
    { 
     this.a = a; 
     this.b = b; 
    } 
} 

在此方案中的對象和值的範圍會發生什麼?由於x是一個值類型,因此它通過值傳遞給Bar,因此,沒有任何事情需要發生在其範圍內。但是y發生了什麼?當b被實際評估時,y的值需要堅持返回。是否所有的Foo都會在以後評估y?我只能假設Foo不是GC'ed。

現在讓我們假設我們將Bar序列化到磁盤,然後將其反序列化。實際上已經序列化了什麼?它是否也連接Foo?在Bar被反序列化之後,有什麼魔力讓b可以被評估?你能解釋IL中發生了什麼嗎?

回答

5

更新:看到什麼是真正發生的事情,而不必訴諸於IL:Using reflector to understand anonymous methods and captured variables


當你使用:

public Bar CreateBar() 
{ 
    return new Bar(x,() => y); 
} 

你是隱意this.y;所以就代表而言,它包括參考Foo。因此,Bar(通過代理)的實例會保留整個Foo存活(未垃圾收集),直到Bar可用於收集。

特別是,編譯器不需要(在這種情況下)生成一個額外的類來處理捕獲的變量;唯一需要的是Foo實例,因此可以在Foo上生成一個方法。如果委託涉及局部變量(this除外),這將更加複雜。

就序列化而言......我想說的第一件事是序列化代表是一個非常糟糕的主意。然而,BinaryFormatter散步的代表,你可以(在理論上)結束了一個系列化Bar,序列化的Foo和系列化代表將它們連接 - 但只有如果標記Foo[Serializable]

但我強調 - 這是一個不好主意。我很少使用BinaryFormatter(出於各種原因),但是我使用它的人看到的一個常見問題是「爲什麼要序列化(某種隨機類型)」。通常情況下,答案是「您正在發佈一個事件,並且正嘗試序列化訂閱者」,在這種情況下,最常見的修復方法是將事件的字段標記爲[NonSerialized]


而不是看IL;另一種研究這種方法的方法是在.NET 1.0模式下使用反射器(即不用匿名方法交換);那麼你可以看到:

public Bar CreateBar() 
{ 
    return new Bar(this.x, new Func<int>(this.<CreateBar>b__0)); 
} 
[CompilerGenerated] 
private int <CreateBar>b__0() 
{ 
    return this.y; 
} 

正如你所看到的;傳遞給Bar的東西是在當前實例(this)上隱藏方法的代理(稱爲<CreateBar>b__0())。所以它這個實例到當前Foo那個傳遞給Bar

+0

在C#編程語言(第三版)的第6.5.3節中,有一個與這種情況非常相似的例子,它的處理就像Marc在Foo上編譯器生成的實例方法所解釋的那樣。 – 2009-08-15 14:45:41

+0

夢幻般的答案馬克!謝謝! – 2009-08-18 16:16:39

0

創建一個快速測試項目來輸出值,然後看看它們。它應該回答這些問題,並可能導致你在這個過程中學到一些額外的東西。 (這是大多數會回答你的問題的人所做的。)

+0

我也很好奇IL和它的理論。我已經寫了測試來回答我的一些問題,但我想了解更多實際正在發生的事情。我認爲SO上的一些超級聰明的人可以擺脫一些光。 – 2009-08-14 17:40:35

+0

您可以使用Reflector來查看您的測試項目所生成的IL,但當然,專家的評論將更容易理解。 – 2009-08-14 19:19:53

1

我在嘗試序列化時反映要序列化的對象時出錯。

我的例子:

[Serializable] 
    public class SerializeTest 
    { 
     //public SerializeTest(int a, Func<int> b) 
     //{ 
     // this.a = a; 
     // this.b = b; 
     //} 

     public SerializeTest() 
     { 

     } 

     public int A 
     { 
      get 
      { 
       return a; 
      } 

      set 
      { 
       a = value; 
      } 
     } 
     public Func<int> B 
     { 
      get 
      { 
       return b; 
      } 
      set 
      { 
       b = value; 
      } 
     } 


     #region properties 

     private int a; 
     private Func<int> b; 



     #endregion 

     //serialize itself 
     public string Serialize() 
     { 
      MemoryStream memoryStream = new MemoryStream(); 

      XmlSerializer xs = new XmlSerializer(typeof(SerializeTest)); 
      using (StreamWriter xmlTextWriter = new StreamWriter(memoryStream)) 
      { 
       xs.Serialize(xmlTextWriter, this); 
       xmlTextWriter.Flush(); 
       //xmlTextWriter.Close(); 
       memoryStream = (MemoryStream)xmlTextWriter.BaseStream; 
       memoryStream.Seek(0, SeekOrigin.Begin); 
       StreamReader reader = new StreamReader(memoryStream); 

       return reader.ReadToEnd(); 
      } 
     } 

     //deserialize into itself 
     public void Deserialize(string xmlString) 
     { 
      String XmlizedString = null; 

      using (MemoryStream memoryStream = new MemoryStream()) 
      { 
       using (StreamWriter w = new StreamWriter(memoryStream)) 
       { 
        w.Write(xmlString); 
        w.Flush(); 

        XmlSerializer xs = new XmlSerializer(typeof(SerializeTest)); 
        memoryStream.Seek(0, SeekOrigin.Begin); 
        XmlReader reader = XmlReader.Create(memoryStream); 

        SerializeTest currentConfig = (SerializeTest)xs.Deserialize(reader); 

        this.a = currentConfig.a; 
        this.b = currentConfig.b; 

        w.Close(); 
       } 
      } 
     } 

    } 

class Program 
    { 
     static void Main(string[] args) 
     { 

      SerializeTest test = new SerializeTest() { A = 5, B =()=>67}; 
      string serializedString = test.Serialize(); 


} 
} 

這裏是一個鏈接,這樣做......稍微複雜一些:Serializing Anon Delegates

+0

你說得對,在我的例子中,Foo也需要被標記爲可序列化。所以這意味着它將序列化所有的Foo。 – 2009-08-14 18:07:10

+0

@Stefan:不,您的類都不需要Serializable屬性(至少不用於XML序列化)。此屬性用於與格式化程序(BinaryFormatter,SoapFormatter ...)的序列化。 – 2009-08-14 18:18:39

+0

@Thomas對,我正在使用BinaryFormatter。 – 2009-08-14 18:33:55

0

我覺得在Foo對象x和y將被捕獲的值類型。所以爲該lambda表達式創建的閉包不應該保留對Foo對象的引用。所以編譯器可以爲閉合作爲創建類:

internal class CompilerGeneratedClassName 
{ 
    private int x; 
    private int y; 
    public CompilerGeneratedClassName(int x, int y) 
    { 
    this.x = x; 
    this.y = y; 
    } 

    public int CompilerGeneratedMethodName() 
    { 
    return this.y; 
    }  
} 

return new Bar(x,() => y); 

可以通過

return new Bar(x,new CompilerGeneratedClassName(x,y).CompilerGeneratedMethodName); 

更換所以,我不認爲會有參考作爲此關閉的結果,Foo對象。所以Foo對象可以被GCed。我可能是錯的。你可以做的一件事是編寫一個小程序,編譯它,然後在ILDASM工具中檢查生成的IL。

+0

我已經檢查過IL,但我不是那麼熟悉IL,也不知道編譯器做了什麼特殊的事情。 – 2009-08-14 18:21:49

+1

編譯器不需要關閉類;它是一個字段,而不是本地方法變量。這是通過的'這個'。看到我的答案更多。 – 2009-08-15 08:13:56