2010-08-17 8 views
18

Wenn BinaryFormatter einen Stream in Objekte deserialisiert, scheint es, neue Objekte ohne Aufruf von Konstruktoren zu erstellen.Wie erstellt BinaryFormatter.Deserialize neue Objekte?

Wie macht es das? Und warum? Gibt es sonst noch etwas in .NET?

Hier ist eine Demo:

[Serializable] 
public class Car 
{ 
    public static int constructionCount = 0; 

    public Car() 
    { 
     constructionCount++; 
    } 
} 

public class Test 
{ 
    public static void Main(string[] args) 
    { 
     // Construct a car 
     Car car1 = new Car(); 

     // Serialize and then deserialize to create a second, identical car 
     MemoryStream stream = new MemoryStream(); 
     BinaryFormatter formatter = new BinaryFormatter(); 
     formatter.Serialize(stream, car1); 
     stream.Seek(0, SeekOrigin.Begin); 
     Car car2 = (Car)formatter.Deserialize(stream); 

     // Wait, what happened? 
     Console.WriteLine("Cars constructed: " + Car.constructionCount); 
     if (car2 != null && car2 != car1) 
     { 
      Console.WriteLine("But there are actually two."); 
     } 
    } 
} 

Ausgang:

Cars constructed: 1
But there are actually two.

+0

Gute Frage. Um dies zu umgehen, müssen Sie während der Deserialisierung einige Pointer-/Referenzkorrekturen durchführen, was schwierig oder sogar unmöglich sein kann. Beachten Sie, dass 'New Car' nur einmal aufgerufen wurde. Sie können dies in 2 Prozessen versuchen. – leppie

+0

möglich Duplikat von [DataContractSerializer ruft nicht meinen Konstruktor auf?] (Http://stackoverflow.com/questions/1076730/datacontractserrializer-doesnt-call-my-constructor) –

+2

Hinweis: Die andere Frage, die ich verknüpfte, ist über DataContractSerializer , aber die Erklärung ist das gleiche für BinaryFormatter –

Antwort

3

Die Sache ist, ist BinaryFormatter nicht wirklich Ihre besondere Aufgabe zu machen. Es bringt ein Objektdiagramm zurück in den Speicher. Der Objektgraph ist im Grunde die Darstellung Ihres Objekts im Speicher; Dies wurde erstellt, als das Objekt serialisiert wurde. Der Deserialisierungsaufruf klebt diesen Graph im Grunde nur als Objekt an einem offenen Zeiger im Speicher zurück, und dann wird er an den Code übergeben, der er tatsächlich ist. Wenn es falsch ist, wird eine Ausnahme ausgelöst.

Zu Ihrem speziellen Beispiel konstruieren Sie wirklich nur ein Auto; Sie machen gerade eine genaue Kopie dieses Autos. Wenn Sie es in den Stream serialisieren, speichern Sie eine exakte Binärkopie davon. Wenn Sie es deserialisieren, müssen Sie nichts konstruieren. Es hält nur das Diagramm im Speicher bei einem Zeigerwert als Objekt und lässt Sie damit machen, was Sie wollen.

Ihr Vergleich von car1! = Car2 ist wegen dieser anderen Zeigerposition wahr, da Car ein Referenztyp ist.

Warum? Ehrlich gesagt, ist es einfach, einfach die binäre Darstellung zu ziehen, anstatt jede Eigenschaft und all das zu ziehen.

Ich bin mir nicht sicher, ob irgendetwas anderes in .NET das gleiche Verfahren verwendet; Die wahrscheinlichsten Kandidaten wären alle anderen, die das Binary eines Objekts in einem Format während der Serialisierung verwenden.

17

Es gibt zwei Dinge, die einen Konstruktor aufrufen (oder zumindest tun sollten).

Eine ist es, eine bestimmte Menge an Speicher für das Objekt zur Seite zu legen und alle notwendigen Operationen auszuführen, um ein Objekt für den Rest der .NET-Welt zu sein (beachten Sie eine gewisse Menge an Handwaving in dieser Erklärung).

Die andere ist, das Objekt in einen gültigen Anfangszustand zu bringen, vielleicht basierend auf Parametern - das ist der eigentliche Code im Konstruktor.

Die Deserialisierung macht ähnlich wie der erste Schritt FormatterServices.GetUninitializedObject und führt dann ähnlich wie der zweite Schritt die Werte für Felder so aus, dass sie denen entsprechen, die während der Serialisierung aufgezeichnet wurden (was eine Deserialisierung erforderlich machen kann) andere Objekte zu sagen, Werte).

Der Zustand, in dem die Deserialisierung das Objekt platziert, entspricht möglicherweise nicht dem von einem Konstruktor möglichen Status. Im besten Fall wird es verschwenderisch (alle vom Konstruktor gesetzten Werte werden überschrieben) und im schlimmsten Fall kann es gefährlich sein (Konstruktor hat einen Nebeneffekt). Es könnte auch einfach unmöglich sein (nur Konstruktor ist derjenige, der Parameter nimmt - Serialisierung hat keine Möglichkeit zu wissen, welche Argumente zu verwenden sind).

Man könnte es als eine spezielle Art von Konstruktor betrachten, der nur durch Deserialisierung verwendet wird (OO-Puristen werden - und sollten - bei der Idee eines Konstruktors, der nicht konstruiert, ich meine das nur als Analogie, wenn du das schaust weiß C++, denke an die Art und Weise, wie das Überschreiben new funktioniert so weit wie Speicher geht und Sie haben eine noch bessere Analogie, obwohl immer noch nur eine Analogie).

Nun, das ist ein Problem in einigen Fällen sein kann - vielleicht haben wir readonly Felder, die nur durch einen Konstruktor festgelegt werden kann, oder vielleicht haben wir Nebenwirkungen, dass wir passieren wollen.

Eine Lösung für beide besteht darin, das Serialisierungsverhalten mit ISerializable zu überschreiben. Dies wird serialisieren basierend auf einem Aufruf an ISerializable.GetObjectData und rufen Sie dann einen bestimmten Konstruktor mit SerializationInfo und StreamingContext Felder zu deserialise (der Konstruktor kann auch privat sein - was bedeutet, die meisten anderen Code wird es nicht einmal sehen). Also, wenn wir readonly Felder deserialisieren können und irgendwelche Nebenwirkungen haben, die wir wollen (wir können auch alle möglichen Dinge tun, um zu kontrollieren, was serialisiert ist und wie).

Wenn wir nur sicherstellen wollen, dass bei der Deserialisierung einige Nebeneffekte auftreten, die beim Bau auftreten, können wir IDeserializationCallback implementieren und wir werden IDeserializationCallback.OnDeserialization aufgerufen haben, wenn die Deserialisierung abgeschlossen ist.

Für andere Dinge, die das gleiche tun, gibt es andere Formen der Serialisierung in .NET, aber das ist alles, was ich weiß. Es ist möglich, FormatterServices.GetUninitializedObject selbst aufzurufen, aber ohne einen Fall, in dem Sie sicher sind, dass der nachfolgende Code das erzeugte Objekt in einen gültigen Zustand versetzt (genau die Situation, in der Sie ein Objekt aus Serialisierungsdaten deserialisieren) Art von Objekt) ist so ein Problem und eine gute Möglichkeit, einen wirklich schwer zu diagnostizierenden Fehler zu produzieren.

+1

+1 - IDeserializationCallback ist eine großartige Idee. Verwenden Sie es, um notwendige private Felder usw. zu initialisieren. Gelöst mein Problem! – womp

Verwandte Themen