2009-08-14 10 views
5

Lassen Sie uns sagen, dass ich den folgenden Code haben:Anonyme Methoden, Umfang und Serialisierung

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

Was mit dem Umfang der Objekte und Werte in diesem Szenario geschieht? Da x ein Werttyp ist, wird es nach Wert an Bar übergeben, und daher muss seinem Bereich nichts passieren. Aber was passiert mit dir? Der Wert für y muss beibehalten werden, um zurückgegeben zu werden, wenn b tatsächlich ausgewertet wird. Werden alle von Foo zu einem späteren Zeitpunkt um sie zu bewerten? Ich kann nur davon ausgehen, dass Foo nicht GC'ed ist.

Jetzt sagen wir, dass wir Bar auf Festplatte serialisieren und später deserialisieren. Was wurde eigentlich serialisiert? Hat es Foo auch seriell gemacht? Welche Magie ist passiert, so dass b ausgewertet werden kann, nachdem Bar deserialisiert wurde? Kannst du erklären, was in der IL passiert?

Antwort

5

Update: um zu sehen, was ohne tatsächlich geschieht auf IL zurückgreifen: Using reflector to understand anonymous methods and captured variables


Wenn Sie:

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

Sie implizit this.y sind Sinn; so in Bezug auf den Delegierten, ist es die Referenz zu Foo, die enthalten ist. Als solche behält die Instanz Bar (über den Delegierten) die gesamte Foo am Leben (nicht Müll gesammelt), bis die Bar zur Sammlung verfügbar ist.

Insbesondere (in diesem Fall) ist es nicht notwendig, dass der Compiler eine zusätzliche Klasse zur Verarbeitung der erfassten Variablen generiert; Das einzige Erforderliche ist die Foo -Instanz, so dass eine Methode unter Foo generiert werden kann. Dies wäre komplexer, wenn der Delegierte lokale Variablen (außer this) involviert.

In Bezug auf die Serialisierung ... nun, das erste, was ich sagen würde ist, dass die Serialisierung von Delegierten eine sehr sehr schlechte Idee ist. Allerdings BinaryFormatterwird Spaziergang Delegierten, und Sie können (in der Theorie) am Ende mit einem serialisierten Bar, eine serialisierte Foo und eine serialisierte delegieren sie zu verbinden - aber nur wenn Sie Foo als [Serializable] markieren.

Aber ich betone - das ist eine schlechte Idee. Ich verwende selten BinaryFormatter (aus einer Vielzahl von Gründen), aber eine häufige Frage, die ich von Leuten sehe, die es verwenden, ist "warum versucht es zu serialisieren (irgendein zufälliger Typ)". Normalerweise lautet die Antwort: "Sie veröffentlichen ein Ereignis und versuchen, den Abonnenten zu serialisieren". In diesem Fall würde das am häufigsten anzutreffende Problem darin bestehen, das Feld des Ereignisses als [NonSerialized] zu markieren.


Anstatt IL zu betrachten; Eine andere Möglichkeit, dies zu untersuchen, ist die Verwendung von Reflector im .NET 1.0-Modus (d. h. ohne dass anonyme Methoden ausgetauscht werden). dann können Sie sehen:

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

Wie Sie sehen können; Das an Bar übergebene Objekt ist ein Delegierter für eine verborgene Methode (<CreateBar>b__0()) in der aktuellen Instanz (this). So ist es ist die Instanz der aktuellen Foo, die an die Bar übergeben wird.

+0

In Abschnitt 6.5.3 der C# -Programmiersprache (3. Ausgabe) gibt es ein Beispiel, das diesem Fall sehr ähnlich ist und genauso behandelt wird, wie Marc durch eine vom Compiler erzeugte Instanzmethode auf Foo erklärt wurde. –

+0

Fantastische Antwort Marc! Vielen Dank! –

0

Erstellen Sie ein schnelles Testprojekt, um die Werte auszugeben und betrachten Sie sie dann. Es sollte die Fragen beantworten und wahrscheinlich dazu führen, dass Sie in diesem Prozess etwas lernen. (Dies ist, was die meisten Leute, die Ihre Frage beantworten werden, getan haben.)

+0

Ich bin auch neugierig auf die IL und die Theorie davon. Ich habe Tests geschrieben, um einige meiner Fragen zu beantworten, aber ich würde gerne mehr darüber erfahren, was eigentlich vor sich geht. Ich dachte, ein paar superkluge Leute in SO könnten noch mehr Licht abwerfen. –

+0

Sie können Reflector verwenden, um die IL zu sehen, die von Ihrem Testprojekt erstellt wurde, aber die Kommentare eines Experten wären verständlicher. –

1

Ich habe Fehler beim Versuch zu serialisieren, wenn es das Objekt zu serialisieren widerspiegelt.

mein Beispiel:

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


} 
} 

Hier ist ein Link, es zu tun ... etwas komplexer: Serializing Anon Delegates

+0

Sie haben Recht, in meinem Beispiel muss Foo auch als Serializable markiert werden. Das bedeutet, dass es alles von Foo serialisiert. –

+0

@Stefan: Nein, keine Ihrer Klassen benötigt das Serializable-Attribut (zumindest nicht für die XML-Serialisierung). Dieses Attribut ist für die Serialisierung mit Formatierern (BinaryFormatter, SoapFormatter ...) –

+0

@Thomas Richtig, ich verwende den BinaryFormatter. –

0

Ich denke, x und y in Foo Objekt wird als Werttypen erfasst werden. Daher sollte die für diesen Lambda-Ausdruck erstellte Schließung keinen Verweis auf das Foo-Objekt enthalten. So dass der Compiler eine Klasse für die Schließung wie schaffen kann:

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

und

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

kann ich glaube nicht von

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

ersetzt werden, dass die dort Bezug sein auf das Foo-Objekt als Ergebnis dieser Schließung. Also könnte Foo-Objekt gewertet werden. Ich könnte falsch liegen. Eine Sache, die Sie tun können, ist, ein kleines Programm zu schreiben, es zu kompilieren und die generierte IL in ILDASM-Tool zu überprüfen.

+0

Ich habe die IL untersucht, aber ich bin nicht so vertraut mit IL und kann nichts Besonderes herausfinden, was der Compiler getan hat. –

+1

Der Compiler benötigt dafür keine Schließklasse; Es ist ein Feld, keine lokale Methodenvariable. Es ist das 'This', das passiert ist. Siehe meine Antwort für mehr. –