C # DataContract Сериализация, как десериализовать в уже существующий экземпляр - PullRequest
8 голосов
/ 21 декабря 2009

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

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

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

17 голосов
/ 02 января 2010

Во время десериализации он (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 есть некоторые дополнительные примечания, здесь .

3 голосов
/ 21 декабря 2009

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

Я не уверен насчет точной подписи с Контрактами. Я использовал 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);
    }

}
0 голосов
/ 23 ноября 2012

Нет проблем, просто используйте 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);
0 голосов
/ 21 декабря 2009

Вы можете получить шаг к тому, что ищете, используя 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; Замените наследство тем, что вы на самом деле используете. И наконец, это означает, что вам придется создавать экземпляры этого типа при десериализации ваших объектов, что может включать изменение некоторого кода и / или конфигурации.

...