2009-12-21 10 views
8

Ho una classe, che contiene un dizionario statico di tutte le istanze esistenti, che sono definite in fase di compilazione.Serializzazione C# DataContract, come deserializzare su un'istanza già esistente

In pratica sembra che questo:

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

} 

Non ci sono altri campi, e la classe deriva, ma questo non ha importanza per il problema.

Solo il id è serializzato. Quando un'istanza di questo tipo viene deserializzata, mi piacerebbe ottenere l'istanza creata come campo statico (A, B o C), utilizzando Foo.Get(id) invece di ottenere una nuova istanza.

C'è un modo semplice per farlo? Non ho trovato risorse che potessi capire.

risposta

16

durante la deserializzazione di esso (per quanto ne so) utilizza sempre un nuovo oggetto (FormatterServices.GetUninitializedObject), ma per farlo sostituire gli oggetti dopo deserializzazione (ma prima di essere restituito al chiamante), è possibile implementare IObjectReference, in questo modo:

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

fatto ... la prova:

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

Nota ci sono alcune note aggiuntive su questo per gli scenari di attendibilità parziale su MSDN, here.

3

Avevo un problema simile e la soluzione migliore che ho trovato era l'aggiunta di una classe wrapper, che gestiva le istanze di quella che doveva essere serializzata.

Non sono sicuro della firma esatta con Contratti. Ho usato SerializableAttribute e con esso sono sembrato smth. così:

[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

Io in realtà non voglio la classe usando preoccuparsi, perché 'foo' è una classe molto centrale e utilizzato da molti altri tipi. –

+0

Come sarebbe questa implementazione? –

+0

vedere la mia risposta modificata – ironic

0

Si può essere in grado di ottenere un passo verso quello che stai cercando per l'utilizzo di OnDeserializingAttribute. Tuttavia, che probabilmente solo consente di impostare le proprietà (così si potrebbe avere ciò che equivale a un metodo di copia che popola tutte le proprietà dell'istanza corrente utilizzando l'istanza statica.

Penso che se si vuole realmente tornare le istanze statiche , si sarebbe probabilmente necessario scrivere il proprio Deserializer ...

testato, ma vorrei assumere è possibile implementare un deserializzatore abbastanza facilmente in questo modo:

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

Nota che dovrete fare qualcosa su come ottenere l'ID dal momento che è privato nel codice, inoltre si presume che si stia utilizzando la serializzazione XML; Sostituisci l'ereditarietà con qualsiasi cosa tu stia effettivamente utilizzando. Infine, questo significa che dovrai istanziare questo tipo quando deserializzi i tuoi oggetti, il che potrebbe comportare la modifica di codice e/o configurazione.

+0

Conosco l'attributo 'OnDeserializing'. Non consente la creazione dell'istanza, perché è richiamata sull'istanza già creata. C'è anche l'interfaccia 'ISerializable' e altre cose, ma non riesco a trovare una soluzione per questo. –

+0

Vedere ulteriori informazioni sulla creazione del proprio deserializzatore. –

+0

Ne ho bisogno per WCF (NetDataContractSerializer). Potrei * usare un altro serializzatore per l'intera applicazione, ma vorrei mantenere l'infrastruttura il più possibile. –

0

Nessun problema, basta usare 2 classi.Nel metodo getObject a ottenere il vostro oggetto esistente

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

Poi, quando deserializzare:

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