2009-12-21 7 views
8

J'ai une classe qui contient un dictionnaire statique de toutes les instances existantes, qui sont définies au moment de la compilation.C# DataContract Sérialisation, désérialisation d'une instance déjà existante

Fondamentalement, il ressemble à ceci:

[DataContract] 
class Foo 
{ 
    private static Dictionary<long, Foo> instances = new Dictionary<long, Foo>(); 

    [DataMember] 
    private long id; 

    public static readonly Foo A = Create(1); 
    public static readonly Foo B = Create(2); 
    public static readonly Foo C = Create(3); 

    private static Foo Create(long id) 
    { 
    Foo instance = new Foo(); 
    instance.id = id; 
    instances.Add(instance); 
    return instance; 
    } 

    public static Foo Get(long id) 
    { 
    return instances[id]; 
    }  

} 

Il y a d'autres domaines, et la classe est dérivée, mais cela n'a pas d'importance pour le problème.

Seul le id est sérialisé. Quand une instance de ce type est désérialisée, je voudrais obtenir l'instance qui a été créée en tant que champ statique (A, B ou C), en utilisant Foo.Get(id) au lieu d'obtenir une nouvelle instance.

Existe-t-il un moyen simple de faire cela? Je n'ai trouvé aucune ressource que j'ai pu comprendre.

Répondre

16

Au cours de désérialisation il (AFAIK) utilise toujours un nouvel objet (FormatterServices.GetUninitializedObject), mais pour l'obtenir de remplacer les objets après désérialisation (mais avant qu'ils ne soient renvoyés à l'appelant), vous pouvez mettre en œuvre IObjectReference, comme suit:

[DataContract] 
class Foo : IObjectReference { // <===== implement an extra interface 
    object IObjectReference.GetRealObject(StreamingContext ctx) { 
     return Get(id); 
    } 
    ...snip 
} 

fait ... la preuve:

static class Program { 
    static void Main() { 
     Foo foo = Foo.Get(2), clone; 
     DataContractSerializer ser = new DataContractSerializer(typeof(Foo)); 
     using (MemoryStream ms = new MemoryStream()) { // clone it via DCS 
      ser.WriteObject(ms, foo); 
      ms.Position = 0; 
      clone = (Foo)ser.ReadObject(ms); 
     } 
     Console.WriteLine(ReferenceEquals(foo, clone)); // true 
    } 
} 

Remarque il y a quelques notes supplémentaires sur ce pour les scénarios de confiance partielle sur MSDN, here.

3

J'ai eu un problème similaire et la meilleure solution que j'ai trouvée était d'ajouter une classe wrapper, qui gérait les instances de celle qui devait être sérialisée.

Je ne suis pas sûr de la signature exacte avec les contrats. J'ai utilisé SerializableAttribute, et avec elle j'ai regardé smth. comme cela:

[Serializable] 
class FooSerializableWrapper : ISerializable 
{ 
    private readonly long id; 

    public Foo Foo 
    { 
     get 
     { 
      return Foo.Get(id); 
     } 
    } 

    public FooSerializableWrapper(Foo foo) 
    { 
     id = foo.id; 
    } 

    protected FooSerializableWrapper(SerializationInfo info, StreamingContext context) 
    { 
     id = info.GetInt64("id"); 
    } 


    void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     info.AddValue("id", id); 
    } 

} 
+0

Je ne veux pas en fait la classe en utilisant concernés, parce que 'foo' est une classe très central et utilisé à partir beaucoup d'autres types. –

+0

A quoi ressemblerait cette implémentation? –

+0

s'il vous plaît voir ma réponse éditée – ironic

0

Vous pouvez faire un pas vers ce que vous cherchez en utilisant OnDeserializingAttribute. Cependant, cela probablement vous permettre de définir les propriétés (de sorte que vous pourriez avoir ce qui équivaut à une méthode de copie qui renseigne toutes les propriétés de l'instance en cours en utilisant votre instance statique.

Je pense que si vous voulez vraiment retourner vos instances statiques , vous auriez sans doute d'écrire votre propre désérialiseur ...

Untested, mais je suppose que vous pourriez mettre en œuvre un désérialiseur comme assez facilement ceci:

public class MyDeserializer : System.Xml.Serialization.XmlSerializer 
{ 
    protected override object Deserialize(System.Xml.Serialization.XmlSerializationReader reader) 
    { 
     Foo obj = (Foo)base.Deserialize(reader); 
     return Foo.Get(obj.id); 
    } 
} 

Notez que vous devez faire quelque chose à propos de l'obtention de l'ID, car il est privé dans votre code; Cela suppose également que vous utilisez la sérialisation XML; Remplacez l'héritage par ce que vous utilisez réellement. Et enfin, cela signifie que vous devrez instancier ce type lors de la désérialisation de vos objets, ce qui peut impliquer de changer de code et/ou de configuration.

+0

Je connais l'attribut 'OnDeserializing'. Il ne permet pas de créer l'instance, car elle est appelée sur l'instance déjà créée. Il y a aussi l'interface 'ISerializable' et d'autres choses, mais je ne trouve pas de solution pour ça. –

+0

Pour plus d'informations sur la construction de votre propre désérialiseur. –

+0

J'en ai besoin pour WCF (NetDataContractSerializer). Je * pourrais * utiliser un autre sérialiseur pour l'ensemble de l'application, mais je voudrais garder l'infrastructure autant que possible. –

0

Pas de problème, utilisez simplement 2 classes.Dans la méthode getObject vous obtenez votre objet existant

[Serializable] 
public class McRealObjectHelper : IObjectReference, ISerializable 
{ 
    Object m_realObject; 
    virtual object getObject(McObjectId id) 
    { 
     return id.GetObject(); 
    } 
    public McRealObjectHelper(SerializationInfo info, StreamingContext context) 
    { 
     McObjectId id = (McObjectId)info.GetValue("ID", typeof(McObjectId)); 
     m_realObject = getObject(id); 
     if(m_realObject == null) 
      return; 
     Type t = m_realObject.GetType(); 
     MemberInfo[] members = FormatterServices.GetSerializableMembers(t, context); 
     List<MemberInfo> deserializeMembers = new List<MemberInfo>(members.Length); 
     List<object> data = new List<object>(members.Length); 
     foreach(MemberInfo mi in members) 
     { 
      Type dataType = null; 
      if(mi.MemberType == MemberTypes.Field) 
      { 
       FieldInfo fi = mi as FieldInfo; 
       dataType = fi.FieldType; 
      } else if(mi.MemberType == MemberTypes.Property){ 
       PropertyInfo pi = mi as PropertyInfo; 
       dataType = pi.PropertyType; 
      } 
      try 
      { 
       if(dataType != null){ 
        data.Add(info.GetValue(mi.Name, dataType)); 
        deserializeMembers.Add(mi); 
       } 
      } 
      catch (SerializationException) 
      { 
       //some fiels are missing, new version, skip this fields 
      } 
     } 
     FormatterServices.PopulateObjectMembers(m_realObject, deserializeMembers.ToArray(), data.ToArray()); 
    } 

    public object GetRealObject(StreamingContext context) 
    { 
     return m_realObject; 
    } 
    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] 
    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
    } 
} 

public class McRealObjectBinder: SerializationBinder 
{ 
    String assemVer; 
    String typeVer; 
    public McRealObjectBinder(String asmName, String typeName) 
    { 
     assemVer = asmName; 
     typeVer = typeName; 
    } 
    public override Type BindToType(String assemblyName, String typeName) 
    { 
     Type typeToDeserialize = null; 
     if (assemblyName.Equals(assemVer) && typeName.Equals(typeVer)) 
     { 
      return typeof(McRealObjectHelper); 
     } 
     typeToDeserialize = Type.GetType(String.Format( "{0}, {1}", typeName, assemblyName)); 
     return typeToDeserialize; 
    } 
} 

Puis, quand deserialize:

BinaryFormatter bf = new BinaryFormatter(null, context); 
bf.Binder = new McRealObjectBinder(YourType.Assembly.FullName, YourType.FullName); 
bf.Deserialize(memStream);