C# DataContract Сериализация, как десериализовать в уже существующий экземпляр

У меня есть класс, который содержит статический словарь всех существующих экземпляров, которые определены во время компиляции.

В основном это выглядит так:

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

}

Есть другие поля, и класс является производным, но это не имеет значения для проблемы.

Только id сериализуется. Когда экземпляр этого типа десериализован, я хотел бы получить экземпляр, который был создан как статическое поле (A, B или же C), с помощью Foo.Get(id) вместо того, чтобы получить новый экземпляр.

Есть ли простой способ сделать это? Я не нашел никаких ресурсов, которые смог бы понять.

4 ответа

Решение

Во время десериализации он (AFAIK) всегда использует новый объект (FormatterServices.GetUninitializedObject), но чтобы заставить его заменить объекты после десериализации (но до того, как они будут возвращены вызывающей стороне), вы можете реализовать IObjectReference, вот так:

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

сделано... доказательство:

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

Обратите внимание, что здесь есть несколько дополнительных примечаний для сценариев частичного доверия в MSDN.

У меня была похожая проблема, и лучшим решением, которое я нашел, было добавление некоторого класса-обертки, который управлял экземплярами того, который нужно было сериализовать.

Я не уверен в точной подписи с Контрактами. Я использовал SerializableAttribute, и с ним я смотрел что-то. как это:

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

}

Вы можете сделать шаг к тому, что вы ищете, используя OnDeserializingAttribute. Однако это, вероятно, позволит вам только установить свойства (так что вы можете иметь то, что составляет метод Copy, который заполняет все свойства текущего экземпляра, используя ваш статический экземпляр.

Я думаю, что если вы действительно хотите вернуть статические экземпляры, вам, вероятно, придется написать свой собственный десериализатор...

Не проверено, но я предполагаю, что вы могли бы легко внедрить десериализатор, например так:

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

Обратите внимание, что вам придется что-то предпринять для получения идентификатора, поскольку он является частным в вашем коде; Также предполагается, что вы используете сериализацию XML; Замените наследство тем, что вы на самом деле используете. И, наконец, это означает, что вам придется создавать экземпляры этого типа при десериализации ваших объектов, что может включать изменение некоторого кода и / или конфигурации.

Нет проблем, просто используйте 2 класса. В методе getObject вы получаете существующий объект

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

Затем при десериализации:

BinaryFormatter bf = new BinaryFormatter(null, context);
bf.Binder = new McRealObjectBinder(YourType.Assembly.FullName, YourType.FullName);
bf.Deserialize(memStream);
Другие вопросы по тегам