Comment BinaryFormatter.Désérialiser créer de nouveaux objets?
Quand BinaryFormatter
désérialise un flux de données dans des objets, il semble pour créer de nouveaux objets sans appel des constructeurs.
Comment est-il fait cela? Et pourquoi? Est-il rien d'autre dans .NET qui fait cela?
Voici une démo:
[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.");
}
}
}
De sortie:
Cars constructed: 1
But there are actually two.
- Bonne question. Pour contourner ce problème, vous aurez besoin de faire quelques pointeur/référence corrections lors de la désérialisation, ce qui peut être difficile ou même impossible. Remarque le fait, que
new Car
a été appelé une fois. Vous pourriez veux essayer ce dans les 2 processus. - double possible de DataContractSerializer ne pas appeler mon constructeur ??
- Remarque: L'autre question, je lien est sur le DataContractSerializer, mais l'explication est la même pour BinaryFormatter
- Merci, cela répond-il. FormatterServices.GetUninitializedObject() est bizarre.
Vous devez vous connecter pour publier un commentaire.
Il y a deux choses l'appel d'un constructeur (ou au moins devrait le faire).
Est de mettre de côté une certaine quantité de mémoire pour l'objet et fait tout le ménage nécessaire pour être un objet pour le reste de la .Monde NET (note certaine quantité de handwaving dans cette explication).
L'autre est de mettre l'objet dans un valide de l'état initial, peut-être basé sur les paramètres - c'est ce que le code dans le constructeur va faire.
Deserialisation fait bien la même chose que la première étape en appelant
FormatterServices.GetUninitializedObject
, et puis la même chose que la deuxième étape en définissant les valeurs pour les champs équivalentes à celles qui ont été enregistrées lors de la sérialisation (ce qui peut nécessiter deserialising d'autres objets à dit les valeurs).Maintenant, l'état qui deserialisation est de mettre l'objet dans peut ne pas correspondre à celle qui est possible par n'importe quel constructeur. Au mieux, il sera gaspillage (toutes les valeurs définies par le constructeur sera remplacé), et au pire, cela peut être dangereux (constructeur a certains effets secondaires). Il pourrait aussi être simplement impossible (le seul constructeur est celui qui prend des paramètres - sérialisation n'a aucun moyen de savoir quels sont les arguments à utiliser).
On pourrait considérer comme une sorte de constructeur utilisé uniquement par deserialisation (OO les puristes - et devrait - tremble à l'idée d'un constructeur qui n'a pas de construire, je veux dire cela comme une analogie seulement, si vous connaissez le C++ réfléchir à la manière primordiale
new
fonctionne aussi loin que la mémoire va et vous avez une bien meilleure analogie, bien que toujours juste une analogie).Maintenant, cela peut être un problème dans certains cas - peut-être que nous avons
readonly
champs qui ne peut être réglé que par un constructeur, ou peut-être que nous ont des effets de bord que nous voulez arriver.Une solution à la fois est de remplacer la sérialisation comportement avec
ISerializable
. Ce sera serialise basé sur un appel àISerializable.GetObjectData
et puis d'appeler un constructeur particulier avecSerializationInfo
etStreamingContext
champs de deserialise (dit le constructeur peut même être privé du sens de la plupart des autres code n'aurez même pas le voir). Par conséquent, si nous pouvons deserialisereadonly
des champs et avoir tous les effets secondaires que nous voulons (on peut aussi faire toutes sortes de choses à contrôler tout ce qui est sérialisé et comment).Si nous occuper seulement d'assurer certains effets secondaires qui se passe sur deserialisation ce qui allait se passer sur la construction, nous pouvons mettre en œuvre
IDeserializationCallback
et nous auronsIDeserializationCallback.OnDeserialization
appelé lorsque la deserialisation est terminée.Comme pour d'autres choses qui font la même chose, il y existe d'autres formes de sérialisation dans .NET, mais c'est tout ce que je sais de la. Il est possible d'appeler
FormatterServices.GetUninitializedObject
vous-même, mais à moins d'un cas où vous avez une forte garantie que par la suite, le code sera mis à l'objet produit dans un état valide (c'est à dire précisément le genre de situation que vous êtes quand deserialising un objet à partir de données produites par serialising le même genre d'objet) de faire de tels est lourde et une bonne façon de produire un vraiment difficile à diagnostiquer bug.La chose est, BinaryFormatter n'est pas vraiment faire de votre objet en particulier. Il s'agit de mettre un objet graphique de retour dans sa mémoire. L'objet graphique est fondamentalement la représentation de l'objet dans la mémoire; il a été créé lorsque l'objet est sérialisé. Ensuite, le désérialiser appel fondamentalement juste des bâtons qui graphique en arrière dans la mémoire comme un objet lors d'un pointeur, puis elle est castée pour ce qu'elle est réellement par le code. Si c'est coulé mal, alors une exception est levée.
Quant à votre exemple, vous n'êtes vraiment la construction d'une voiture; vous êtes juste faire une copie exacte de la voiture. Lors de la sérialisation de l'eau dans le ruisseau, vous stockez un binaire exacte copie de celui-ci. Lorsque vous désérialiser, vous n'avez pas à construire quoi que ce soit. Il ne s'en tient qu'au graphique de la mémoire à une certaine valeur de pointeur comme un objet et vous permet de faire ce que vous voulez avec elle.
Votre comparaison de car1 != les personnages car2 est vrai, parce que de la que les différentes emplacement du pointeur, puisque la Voiture est un type de référence.
Pourquoi? Franchement, il est facile de simplement aller de tirer la représentation binaire, plutôt que d'avoir à aller et tirer chacun de la propriété et de tout ce qui.
Je ne suis pas sûr qu'autre chose .NET utilise la même procédure; les candidats les plus probables seraient rien d'autre qui utilise un objet binaire dans un format lors de la sérialisation.
Ne sais pas pourquoi le constructeur n'a pas appelé, mais j'utilise
IDeserializationCallback
comme un travail autour de.aussi jeter un oeil à
OnSerializingAttribute
OnSerializedAttribute
OnDeserializingAttribute
OnDeserializedAttribute