Штатная десериализация .net всегда создает граф новых объектов. Это не всегда удобно.
- Например если объекты содержат несериализуемые данные, открытые хэндлы и прочее.
- Объекты не попадающие в сериализацию могут иметь ссылки на зачитываемые объекты и т.п. Особенно это актуально, если ваша сборка используется еще кем то, и вы не можете решить все подобные случаи при помощи правильного дизайна.
- И в конце концов, ради небольшого Undo полностью пересоздавать объекты нерационально.
Поиск не дал готового ответа. Есть не самые простые решения с использованием protobuf и прочих сторонних сериализаторов, но это не всегда применимо.
Задача в целом несложная, и мое решение не является чем то выдающимся, но с другой стороны, тем кто впервые столкнется с похожей проблемой — будет проще.
Сериализация делается как обычно. Следующие 2 класса решат проблему при десериализации.
[Serializable]
public class RealObjectHelper : IObjectReference, ISerializable
{
Object m_realObject;
virtual object getObject(ObjectId id)
{
//Этот метод должен возвращать ваш объект,
return id.GetObject();
}
public RealObjectHelper(SerializationInfo info, StreamingContext context)
{
ObjectId id = (ObjectId)info.GetValue("ID", typeof(ObjectId));
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 RealObjectBinder: SerializationBinder
{
String assemVer;
String typeVer;
public RealObjectBinder(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(RealObjectHelper);
}
typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) );
return typeToDeserialize;
}
}
При десериализации надо установить Binder, который создаст обертку для десериализации в ваш существующий объект.
BinaryFormatter bf = new BinaryFormatter(null, context);
bf.Binder = new RealObjectBinder(YourType.Assembly.FullName, YourType.FullName);
bf.Deserialize(memStream);
Автор: mdaemon