2010-04-26 13 views
3

J'écris ma propre implémentation IFormatter et je ne peux pas trouver de moyen de résoudre les références circulaires entre deux types qui implémentent ISerializable.Résolution de références circulaires pour les objets implémentant ISerializable

Voici le schéma habituel:

[Serializable] 
class Foo : ISerializable 
{ 
    private Bar m_bar; 

    public Foo(Bar bar) 
    { 
     m_bar = bar; 
     m_bar.Foo = this; 
    } 

    public Bar Bar 
    { 
     get { return m_bar; } 
    } 

    protected Foo(SerializationInfo info, StreamingContext context) 
    { 
     m_bar = (Bar)info.GetValue("1", typeof(Bar)); 
    } 

    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     info.AddValue("1", m_bar); 
    } 
} 

[Serializable] 
class Bar : ISerializable 
{ 
    private Foo m_foo; 

    public Foo Foo 
    { 
     get { return m_foo; } 
     set { m_foo = value; } 
    } 

    public Bar() 
    { } 

    protected Bar(SerializationInfo info, StreamingContext context) 
    { 
     m_foo = (Foo)info.GetValue("1", typeof(Foo)); 
    } 

    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     info.AddValue("1", m_foo); 
    } 
} 

-je alors ceci:

Bar b = new Bar(); 
Foo f = new Foo(b); 
bool equal = ReferenceEquals(b, b.Foo.Bar); // true 

// Serialise and deserialise b 

equal = ReferenceEquals(b, b.Foo.Bar); 

Si j'utilise un hors-the-box BinaryFormatter à serialise et deserialise b, le test ci-dessus pour reference-equality renvoie true comme on pourrait s'y attendre. Mais je ne peux pas concevoir un moyen d'y parvenir dans mon IFormatter personnalisé.

Dans une situation non-ISerializable je peux simplement revoir les champs d'objet "en attente" en utilisant la réflexion une fois que les références cibles ont été résolues. Mais pour les objets implémentant ISerializable, il n'est pas possible d'injecter de nouvelles données en utilisant SerializationInfo.

Quelqu'un peut-il me diriger dans la bonne direction?

Répondre

0

Vous devez détecter que vous avez utilisé le même objet plus d'une fois dans votre graphe d'objets, étiqueter chaque objet dans la sortie, et quand vous venez à occurance # 2 ou plus, vous devez fournir une "référence" à une balise existante à la place de l'objet une fois de plus.

pseudo-code pour la sérialisation:

for each object 
    if object seen before 
     output tag created for object with a special note as "tag-reference" 
    else 
     create, store, and output tag for object 
     output tag and object 

Pseudo-code pour désérialisation:

while more data 
    if reference-tag to existing object 
     get object from storage keyed by the tag 
    else 
     construct instance to deserialize into 
     store object in storage keyed by deserialized tag 
     deserialize object 

Il est important que vous faites les dernières étapes là-bas dans l'ordre dans lequel ils sont spécifiés, de sorte que vous pouvez corriger gérer ce cas:

SomeObject obj = new SomeObject(); 
obj.ReferenceToSomeObject = obj; <-- reference to itself 

ie. Vous ne pouvez pas stocker l'objet dans votre stockage de tag après l'avoir complètement désérialisé, car vous pourriez avoir besoin d'une référence dans le stockage pendant que vous le désérialisez.

+0

Je comprends votre point de vue sur les "tags de référence" et mon formateur utilise déjà cette technique. Ainsi, votre exemple d'autoréférence n'est pas un problème pour moi. Mais je ne vois pas comment votre réponse m'aide avec les objets implémentant ISerializable qui se réfèrent mutuellement. Êtes-vous capable de résoudre ce problème spécifique? Merci. – Chris

+0

Pas tout à fait sûr de ce que vous voulez dire. Parlez-vous d'utiliser le constructeur privé lié à la sérialisation? –

+0

Oui, exactement. La seule façon de gonfler un objet qui implémente ISerializable est d'appeler son constructeur spécial. – Chris

5

Cette situation est la raison de la méthode FormatterServices.GetUninitializedObject. L'idée générale est que si vous avez des objets A et B qui se mettent en référence dans leur SerializationInfo, vous pouvez les désérialiser comme suit:

(Aux fins de cette explication, (SI,SC) fait référence à un constructeur de désérialisation de types, à savoir celui qui prend une SerializationInfo et un StreamingContext.)

  1. choisir un objet à désérialiser premier. Peu importe ce que vous choisissez, tant que vous n'en choisissez pas un qui est un type de valeur. Disons que vous choisissez A.
  2. Appelez GetUninitializedObject à allouer (mais pas initialiser) une instance de type A, car vous n'êtes pas encore prêt à appeler son constructeur (SI,SC).
  3. Construisez B de la manière habituelle, c'est-à-dire créez un objet SerializationInfo (qui inclura la référence au A désormais à moitié désérialisé) et transmettez-le au constructeur (SI,SC) de B.
  4. Maintenant vous avez toutes les dépendances dont vous avez besoin pour initialiser votre objet A alloué. Créez son objet SerializationInfo et appelez le constructeur (SI,SC) de A. Vous pouvez appeler un constructeur sur une instance existante via la réflexion.

La méthode GetUninitializedObject est pure magie CLR - elle crée une instance sans jamais appeler un constructeur pour initialiser cette instance. Il définit fondamentalement tous les champs à zéro/null.

C'est la raison pour laquelle vous êtes averti de ne pas utiliser les membres d'un objet enfant dans un constructeur (SI,SC) - un objet enfant peut être alloué mais pas encore initialisé à ce stade. C'est également la raison de l'interface IDeserializationCallback, qui vous donne la possibilité d'utiliser vos objets enfants après que toute l'initialisation de l'objet soit garantie et avant que le graphique d'objet désérialisé ne soit retourné.

La classe ObjectManager peut faire tout cela (et d'autres types de corrections) pour vous. Cependant, je l'ai toujours trouvé sous-documenté étant donné la complexité de la désérialisation, donc je n'ai jamais passé le temps d'essayer de comprendre comment l'utiliser correctement. Il utilise un peu plus de magie pour faire l'étape 4 en utilisant une réflexion interne-à-la-CLR optimisée pour appeler le constructeur (SI,SC) plus rapidement (je l'ai chronométré à environ deux fois plus vite que la voie publique). Enfin, il existe des graphes d'objets impliquant des cycles impossibles à désérialiser. Un exemple est quand vous avez un cycle de deux IObjectReference instances (j'ai testé BinaryFormatter sur ceci et il jette une exception). Un autre je soupçonne est si vous avez un cycle involving nothing but boxed value-types.

Questions connexes