2009-12-21 5 views
8

Ich habe eine Klasse, die ein statisches Verzeichnis aller vorhandenen Instanzen enthält, die zur Kompilierungszeit definiert sind.C# DataContract Serialisierung, wie zu bereits vorhandenen Instanz deserialisieren

Grundsätzlich sieht es wie folgt aus:

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

} 

Es gibt andere Felder, und die Klasse abgeleitet wird, aber dies für das Problem spielt keine Rolle.

Nur die id ist serialisiert. Wenn eine Instanz dieses Typs deserialisiert wird, möchte ich die Instanz, die erstellt wurde, als das statische Feld (A, B oder C) mit Foo.Get(id) abrufen, anstatt eine neue Instanz zu erhalten.

Gibt es eine einfache Möglichkeit, dies zu tun? Ich habe keine Ressourcen gefunden, die ich verstehen konnte.

Antwort

16

Bei der Deserialisierung es (AFAIK) immer verwendet ein neues Objekt (FormatterServices.GetUninitializedObject), aber es zu bekommen die Objekte nach Deserialisierung zu ersetzen (aber, bevor sie an den Aufrufer zurückgegeben werden), können Sie IObjectReference implementieren, etwa so:

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

done ... Beweis:

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

Hinweis gibt es einige zusätzliche Hinweise dazu für teilweise vertrauenswürdigen Szenarien auf MSDN, here.

3

Ich hatte ein ähnliches Problem und die beste Lösung, die ich fand, war das Hinzufügen einer Wrapper-Klasse, die Instanzen desjenigen verwaltet, der serialisiert werden musste.

Ich bin mir nicht sicher über die genaue Signatur mit Verträgen. Ich habe SerializableAttribute benutzt, und damit habe ich etwas ausgesehen. dass wie:

[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

Ich will eigentlich nicht die Verwendung von Klasse betroffen sein, weil 'foo' eine sehr zentrale Klasse und verwendet aus viele andere Arten. –

+0

Wie würde diese Implementierung aussehen? –

+0

siehe meine bearbeitete Antwort – ironic

0

können Sie in der Lage sein, einen Schritt zu bekommen auf das, was Sie suchen OnDeserializingAttribute verwenden. Das lässt Sie aber wahrscheinlich nur Eigenschaften setzen (Sie könnten also eine Kopie haben, die alle Eigenschaften der aktuellen Instanz mit Ihrer statischen Instanz auffüllt.)

Ich denke, wenn Sie Ihre statischen Instanzen tatsächlich zurückgeben möchten müssen, dann würden Sie wahrscheinlich Ihren eigenen Deserializer schreiben ...

Ungeprüfte, aber ich würde annehmen, dass Sie einen Deserializer ziemlich leicht wie diese implementieren könnten:

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

Beachten Sie, dass Sie etwas tun müssen Informationen zum Abrufen der ID, da diese in Ihrem Code privat ist. Dies setzt auch voraus, dass Sie die XML-Serialisierung verwenden. Ersetzen Sie die Vererbung mit dem, was Sie tatsächlich verwenden. Und schließlich bedeutet dies, dass Sie diesen Typ bei der Deserialisierung Ihrer Objekte instanziieren müssen, was dazu führen kann, dass Code und/oder Konfiguration geändert werden müssen.

+0

Ich kenne das 'OnDeserializing' Attribut. Das Erstellen der Instanz ist nicht zulässig, da sie für die bereits erstellte Instanz aufgerufen wird. Es gibt auch die 'ISerializable' Schnittstelle und andere Sachen, aber ich kann keine Lösung dafür finden. –

+0

Weitere Informationen zum Erstellen eines eigenen Deserializers finden Sie unter. –

+0

Ich brauche es für WCF (NetDataContractSerializer). Ich könnte * einen anderen Serialisierer für die gesamte Anwendung verwenden, aber ich möchte die Infrastruktur so gut wie möglich behalten. –

0

Kein Problem, verwenden Sie nur 2 Klassen.In getObject Methode erhalten Sie Ihr vorhandenes Objekt

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

Dann, wenn deserialize:

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