Сериализация 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, и с ним я выглядел smth. вот так:

[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. Однако это, вероятно, позволит вам установить только свойства (чтобы вы могли иметь то, что составляет метод копирования, который заполняет все свойства текущего экземпляра, используя статический экземпляр.

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

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

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