2009-12-21 16 views
8

Tengo una clase, que contiene un diccionario estático de todas las instancias existentes, que se definen en tiempo de compilación.C# DataContract Serialización, cómo deserializar a la instancia ya existente

Básicamente se parece a esto:

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

} 

Existen otros campos, y la clase se deriva, pero esto no tiene importancia para el problema.

Solo se serializa id. Cuando se deserializa una instancia de este tipo, me gustaría obtener la instancia que se ha creado como campo estático (A, B o C), usando Foo.Get(id) en lugar de obtener una nueva instancia.

¿Hay una manera simple de hacer esto? No encontré ningún recurso que pude entender.

Respuesta

16

Durante la deserialización ella (que yo sepa) siempre utiliza un nuevo objeto (FormatterServices.GetUninitializedObject), pero para llegar a sustituir los objetos después deserialización (pero antes de que sean devueltos a la persona que llama), se puede implementar IObjectReference, así:

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

hecho ... prueba:

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 hay algunas notas adicionales sobre este para escenarios de confianza parcial en MSDN, here.

3

Tuve un problema similar y la mejor solución que encontré fue agregando alguna clase de contenedor, que era administrar instancias de la que se necesitaba serializar.

No estoy seguro acerca de la firma exacta con los contratos. Usé SerializableAttribute, y con él parecía smth. así:

[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

que en realidad no quieren preocuparse de la clase utilizando, porque 'foo' es una clase muy central y utilizar desde muchos otros tipos. –

+0

¿Cómo se vería esta implementación? –

+0

por favor vea mi respuesta editada – ironic

0

Usted puede ser capaz de obtener un paso hacia lo que está buscando usando OnDeserializingAttribute. Sin embargo, eso probablemente solo le permita establecer propiedades (por lo que podría tener lo que equivale a un método de copia que rellena todas las propiedades de la instancia actual utilizando su instancia estática.

Creo que si realmente desea devolver sus instancias estáticas , lo que probablemente tiene que escribir su propia deserializador ...

no probado, pero yo supongo que podría implementar un deserializer muy fácilmente de esta manera:

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

tenga en cuenta que usted tiene que hacer algo acerca de cómo obtener la ID, ya que es privada en su código; También esto supone que está utilizando la serialización de XML; Reemplace la herencia con lo que sea que esté usando en realidad. Y, por último, esto significa que tendrá que instanciar este tipo al deserializar sus objetos, lo que puede implicar cambiar algún código y/o configuración.

+0

Conozco el atributo 'OnDeserializing'. No permite crear la instancia, porque se invoca en la instancia ya creada. También está la interfaz 'ISerializable' y otras cosas, pero no puedo encontrar una solución para esto. –

+0

Ver información adicional sobre la construcción de su propio deserializador. –

+0

Lo necesito para WCF (NetDataContractSerializer). Yo * podría * usar otro serializador para toda la aplicación, pero me gustaría mantener la infraestructura tanto como sea posible. –

0

No hay problema, solo use 2 clases.En el método getObject a conseguir su objeto existente

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

Entonces, cuando deserializar:

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