5 Stimmen

Anonyme Methoden, Geltungsbereich und Serialisierung

Nehmen wir an, ich habe den folgenden Code:

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 geschieht mit dem Geltungsbereich der Objekte und Werte in diesem Szenario? Da es sich bei x um einen Werttyp handelt, wird er wertmäßig an Bar übergeben, so dass sich an seinem Geltungsbereich nichts ändern muss. Aber was passiert mit y? Der Wert für y muss bestehen bleiben, um zurückgegeben zu werden, wenn b tatsächlich ausgewertet wird. Bleibt Foo ganz erhalten, um y zu einem späteren Zeitpunkt auszuwerten? Ich kann nur annehmen, dass Foo nicht GC'ed ist.

Nehmen wir an, wir serialisieren Bar auf der Festplatte und deserialisieren es später. Was wurde tatsächlich serialisiert? Wurde auch Foo serialisiert? Welcher Zauber ist geschehen, damit b ausgewertet werden kann, nachdem Bar deserialisiert worden ist? Können Sie erklären, was in der AWL passiert?

5voto

Marc Gravell Punkte 970173

Update: um zu sehen, was tatsächlich passiert, ohne auf IL zurückgreifen zu müssen: Reflektor verwenden, um anonyme Methoden und erfasste Variablen zu verstehen


Wenn Sie verwenden:

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

Sie meinen damit implizit this.y Der Delegierte ist also derjenige, der Referenz a Foo die enthalten ist. Als solches ist die Instanz von Bar (über den Delegierten) hält die gesamte Foo lebendig (nicht müllsammelnd), bis die Bar ist zur Abholung verfügbar.

Insbesondere ist es (in diesem Fall) nicht erforderlich, dass der Compiler eine zusätzliche Klasse für die Behandlung der erfassten Variablen erzeugt; das einzige, was erforderlich ist, ist die Foo Instanz, so dass eine Methode erzeugt werden kann auf Foo . Dies wäre noch komplizierter, wenn der Delegierte lokale Variablen (andere als this ).

In Bezug auf die Serialisierung ... gut, das erste, was ich sagen würde, ist, dass Serialisierung Delegaten eine sehr sehr schlechte Idee ist. Allerdings, BinaryFormatter wird Delegierten gehen, und Sie können (theoretisch) mit einer serialisierten Bar eine serialisierte Foo und einen serialisierten Delegaten, um sie zu verknüpfen - aber nur wenn Sie markieren Foo als [Serializable] .

Aber ich betone - dies 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 benutzen, ist "warum versucht es, (irgendeinen beliebigen Typ) zu serialisieren". In der Regel lautet die Antwort: "Sie veröffentlichen ein Ereignis und versuchen, den Abonnenten zu serialisieren", wobei die häufigste Lösung darin besteht, das Feld des Ereignisses als [NonSerialized] .


Anstatt IL zu betrachten; eine andere Möglichkeit, dies zu untersuchen, besteht darin, Reflektor im .NET 1.0-Modus zu verwenden (d. h. ohne anonyme Methoden einzubinden); 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, ging die Sache an Bar ist ein Delegat für eine versteckte Methode (genannt <CreateBar>b__0() ) auf die aktuelle Instanz ( this ). Es ist also es die Instanz zum aktuellen Foo die an den Bar .

1voto

CSharpAtl Punkte 7190

Ich bekam Fehler beim Versuch zu serialisieren, wenn es das Objekt zu serialisieren reflektiert wurde.

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 dazu... etwas komplexer: Serialisierung von Anon-Delegierten

0voto

John Fisher Punkte 21825

Erstellen Sie ein schnelles Testprojekt, um die Werte auszugeben, und sehen Sie sie sich dann an. Es sollte die Fragen beantworten, und wahrscheinlich lernen Sie dabei auch noch etwas dazu. (Das ist es, was die meisten Leute, die Ihre Frage beantworten werden, getan haben).

0voto

Mehmet Aras Punkte 5166

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

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

et

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

kann ersetzt werden durch

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

Ich glaube also nicht, dass es einen Verweis auf das Foo-Objekt als Ergebnis dieser Schließung geben wird. Das Foo-Objekt könnte also GCed werden. Ich könnte falsch liegen. Eine Möglichkeit ist, ein kleines Programm zu schreiben, es zu kompilieren und die generierte IL im ILDASM-Tool zu überprüfen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X