Как десериализовать существующий объект - С#
В С# после сериализации объекта в файл, как бы десериализовать файл обратно в существующий объект без создания нового объекта?
Все примеры, которые я могу найти для пользовательской сериализации, включают реализацию конструктора, который будет вызван десериализацией, что именно то, что я хочу, за исключением того, что я не хочу, чтобы функция была конструктором.
Спасибо!
Ответы
Ответ 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
Замените строки памяти потоком файлов, и вы получите те же результаты.