Подтвердить что ты не робот

Как десериализовать существующий объект - С#

В С# после сериализации объекта в файл, как бы десериализовать файл обратно в существующий объект без создания нового объекта?

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

Спасибо!

4b9b3361

Ответ 1

Некоторые сериализаторы поддерживают обратные вызовы; например, как BinaryFormatter, так и DataContractSerializer (и protobuf-net, ниже) позволяют указать обратный вызов до-serializaton, и, поскольку они пропускают конструктор, этого вполне может быть достаточно для инициализации объекта. Сериализатор все еще создает его.


Большинство сериализаторов суетливы в желании самостоятельно создать новый объект, однако некоторые из них позволят десериализовать существующий объект. Ну, на самом деле единственное, что бросается в глаза, это protobuf-net (раскрытие: я автор)...

У этого есть 2 различных функции, которые могут помочь здесь; для корневого объекта (т.е. самого внешнего объекта на графике) вы можете напрямую предоставить существующий объект либо методам Merge (в v1, также присутствующим в v2 для совместимости), либо (в v2) методам Deserialize; например:

var obj = Serializer.Merge<YourType>(source, instance);

Однако на более крупном графике вы можете захотеть поставить другие объекты самостоятельно (а не только корень). В API атрибутов не отображается следующее, но это новая функция в v2:

RuntimeTypeModel.Default[typeof(SomeType)].SetFactory(factoryMethod);

где factoryMethod может быть либо именем метода static в SomeType (который возвращает экземпляр SomeType), либо может быть MethodInfo для любого метода static в любом месте. Метод может дополнительно (необязательно) взять сериализацию-контекст в качестве параметра, если вы хотите. Этот метод затем должен использоваться для предоставления всех новых экземпляров SomeType.


Примечание: protobuf-net не совсем то же самое, что BinaryFormatter; для лучшего эффекта вам нужно рассказать о том, как сопоставить своих участников - очень похоже на маркировку вещей как [DataMember] для WCF/DataContractSerializer. Это могут быть атрибуты, но не обязательно.

Ответ 2

Нет проблем, просто используйте 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);

Ответ 3

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

Ответ 4

Это немного необычно, но он работает:

[Serializable]
public class Pets
{
    public int Cats { get; set; }
    public int Dogs { get; set; }
}

public static class Utils
{
    public static byte[] BinarySerialize(object o)
    {
        using (var ms = new MemoryStream())
        {
            IFormatter f = new BinaryFormatter();
            f.Serialize(ms, o);
            return ms.ToArray();
        }
    }

    public static dynamic BinaryDeserialize(byte[] bytes, dynamic o)
    {
        using (var ms = new MemoryStream(bytes))
        {
            ms.Position = 0;
            IFormatter f = new BinaryFormatter();
            o = (dynamic)f.Deserialize(ms);
            return o;
        }
    }
}

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

Pets p = new Pets() { Cats = 0, Dogs = 3 };
Console.WriteLine("{0}, {1}", p.Cats, p.Dogs);
byte[] serial = Utils.BinarySerialize(p);
p.Cats = 1;
Console.WriteLine("{0}, {1}", p.Cats, p.Dogs);
p = Utils.BinaryDeserialize(serial, p);
Console.WriteLine("{0}, {1}", p.Cats, p.Dogs);

Вывод следующий:

0, 3
1, 3
0, 3

Замените строки памяти потоком файлов, и вы получите те же результаты.