在C#中,将对象序列化到文件后,如何在不创建新对象的情况下将文件反序列化为现有对象?
我可以找到的自定义序列化的所有示例都涉及实现一个在反序列化时调用的构造函数,这正是我想要的,除了我不希望函数成为构造函数。
谢谢!
答案 0 :(得分:7)
一些序列化程序支持回调;例如,BinaryFormatter
和DataContractSerializer
(以及下面的protobuf-net)允许您指定before-serializaton回调,并且由于它们跳过构造函数,这很可能足以初始化对象。但序列化程序仍然创建它。
大多数序列化程序都想要自己创建新对象,但有些序列化程序允许您反序列化为现有对象。嗯,实际上唯一一个想到的是protobuf-net(披露:我是作者)......
这有两个不同的功能,可能会有所帮助;对于 root 对象(即图中最外层的对象),您可以将现有对象直接提供给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
完全相同;为了获得最佳效果,您需要告诉它如何映射您的成员 - 非常类似于为WCF / DataContractSerializer标记为[DataMember]
。 可以是属性,但不一定是。
答案 1 :(得分:5)
没问题,只需使用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);
答案 2 :(得分:4)
如果只是复制几个字段的问题,我会避免所有麻烦并采取简单的路径 - 反序列化为新实例,然后将相应的字段复制到现有实例。这将花费你几个额外的副本,但你将节省大量的时间进行调试。
答案 3 :(得分:0)
这有点不寻常,但确实有效:
[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
用文件流替换内存字符串,您将得到相同的结果。