如何通过XML序列化来了解何时加载?

我正在尝试通过XML序列化加载一个对象树,此时它将加载对象,并非常愉快地创建树。 我的问题围绕着这些类支持一定级别的审计这一事实。 我希望能够做的是在每个对象加载完成后调用一些方法。

为了论证,假设我有一个相当通用的对象树,在不同的级别有不同的类,如:

  123 Any Street        456 High Street        

有没有办法使用默认的序列化器(以类似的方式创建像ShouldSerializeFoo这样的方法)来确定每个对象的加载何时完成?

编辑:我应该指出,暴露类似于反序列化后我可以调用的OnLoaded()方法的明显案例,让我觉得这是一件“坏事”。

Edit2:为了讨论起见,这是我当前的hack “方法”,它适用于基本级别,但子City节点仍然认为需要保存更改(在现实世界中,对象模型要复杂得多) ,但这至少会编译,而不需要完整的源代码)

 public class Office { [XmlAttribute("IsHq")] public bool IsHeadquarters { get; set; } [XmlElement] public string Street { get; set; } [XmlElement] public Town Town { get; set; } protected virtual void OnLoaded() {} public static OfficeCollection Search() { OfficeCollection retval = new OfficeCollection(); string xmlString = @"  123 Any Street      "; XmlSerializer xs = new XmlSerializer(retval.GetType()); XmlReader xr = new XmlTextReader(xmlString); retval = (OfficeCollection)xs.Deserialize(xr); foreach (Office thisOffice in retval) { thisOffice.OnLoaded(); } return retval; } } 

嗯……它仍然不是很漂亮,但你可以将你的反序列化逻辑重构为一个专用类,它可以在将反序列化对象返回给调用者之前通知反序列化对象它来自XML。

更新:我认为这应该是相当容易做到的,不会偏离框架所设置的模式……你只需要确保使用CustomXmlSerializer。 需要此通知的类只需要实现IXmlDeserializationCallback

 using System.Xml.Serialization; namespace Custom.Xml.Serialization { public interface IXmlDeserializationCallback { void OnXmlDeserialization(object sender); } public class CustomXmlSerializer : XmlSerializer { protected override object Deserialize(XmlSerializationReader reader) { var result = base.Deserialize(reader); var deserializedCallback = result as IXmlDeserializationCallback; if (deserializedCallback != null) { deserializedCallback.OnXmlDeserialization(this); } return result; } } } 

接受的解决方案对我来说并不适用。 重写的Deserialize()方法永远不会被调用。 我相信这是因为该方法不公开,因此被一个(或多个)公共Deserialize()方法调用,但不是全部。

这是一个通过方法隐藏工作的实现,并利用现有的IDeserializationCallback接口,因此使用非xml方法的任何反序列化仍然可以触发该接口的OnDeserialization()方法。 它还使用reflection来遍历子属性,以查看它们是否也实现了IDeserializationCallback并相应地调用它们。

 using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.Serialization; using System.Xml; using System.Xml.Serialization; namespace Xml.Serialization { class XmlCallbackSerializer : XmlSerializer { public XmlCallbackSerializer(Type type) : base(type) { } public XmlCallbackSerializer(XmlTypeMapping xmlTypeMapping) : base(xmlTypeMapping) { } public XmlCallbackSerializer(Type type, string defaultNamespace) : base(type, defaultNamespace) { } public XmlCallbackSerializer(Type type, Type[] extraTypes) : base(type, extraTypes) { } public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides) : base(type, overrides) { } public XmlCallbackSerializer(Type type, XmlRootAttribute root) : base(type, root) { } public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace) : base(type, overrides, extraTypes, root, defaultNamespace) { } public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, string location) : base(type, overrides, extraTypes, root, defaultNamespace, location) { } public new object Deserialize(Stream stream) { var result = base.Deserialize(stream); CheckForDeserializationCallbacks(result); return result; } public new object Deserialize(TextReader textReader) { var result = base.Deserialize(textReader); CheckForDeserializationCallbacks(result); return result; } public new object Deserialize(XmlReader xmlReader) { var result = base.Deserialize(xmlReader); CheckForDeserializationCallbacks(result); return result; } public new object Deserialize(XmlSerializationReader reader) { var result = base.Deserialize(reader); CheckForDeserializationCallbacks(result); return result; } public new object Deserialize(XmlReader xmlReader, string encodingStyle) { var result = base.Deserialize(xmlReader, encodingStyle); CheckForDeserializationCallbacks(result); return result; } public new object Deserialize(XmlReader xmlReader, XmlDeserializationEvents events) { var result = base.Deserialize(xmlReader, events); CheckForDeserializationCallbacks(result); return result; } public new object Deserialize(XmlReader xmlReader, string encodingStyle, XmlDeserializationEvents events) { var result = base.Deserialize(xmlReader, encodingStyle, events); CheckForDeserializationCallbacks(result); return result; } private void CheckForDeserializationCallbacks(object deserializedObject) { var deserializationCallback = deserializedObject as IDeserializationCallback; if (deserializationCallback != null) { deserializationCallback.OnDeserialization(this); } var properties = deserializedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var propertyInfo in properties) { if (propertyInfo.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null) { var collection = propertyInfo.GetValue(deserializedObject) as IEnumerable; if (collection != null) { foreach (var item in collection) { CheckForDeserializationCallbacks(item); } } } else { CheckForDeserializationCallbacks(propertyInfo.GetValue(deserializedObject)); } } } } } 

我尝试了abatishchev提供的解决方案,但正如他的回答下面的评论所指出的,自定义序列化Deserialize中的Deserialize方法似乎永远不会被调用。

我能够通过重载我需要的所有不同的Deserialize重载来使这个工作,以便它总是调用自定义方法。

 protected object Deserialize(System.IO.StringReader reader) { var result = base.Deserialize(reader); CallBack(result); return result; } protected object Deserialize(System.IO.TextReader reader) { var result = base.Deserialize(reader); CallBack(result); return result; } protected object Deserialize(System.Xml.XmlReader reader) { var result = base.Deserialize(reader); CallBack(result); return result; } protected object Deserialize(System.IO.Stream stream) { var result = base.Deserialize(stream); CallBack(result); return result; } private void CallBack(object result) { var deserializedCallback = result as IXmlDeserializationCallback; if (deserializedCallback != null) { deserializedCallback.OnXmlDeserialization(this); } } 

这样我实际上看到了Deserialize方法被调用。

很难,因为XmlSerializer不支持序列化回调事件。 有什么办法可以使用DataContractSerializer吗? 这样 ,但不允许属性(如上面的@name )。

除此以外; 你可以实现IXmlSerializable ,但这是很多工作,而且非常容易出错。

否则 – 也许通过堆栈检查呼叫者,但这非常脆弱,并且闻起来成熟。

在第一次回答后浪费了一些时间后,我从HotN的post中采用了代码,但CheckForDeserializationCallbacks除外:

 private static void ProcessOnDeserialize(object _result) { var type = _result != null ? _result.GetType() : null; var methods = type != null ? type.GetMethods().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is OnDeserializedAttribute)) : null; if (methods != null) { foreach (var mi in methods) { mi.Invoke(_result, null); } } var properties = type != null ? type.GetProperties().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is XmlElementAttribute || _m is XmlAttributeAttribute)) : null; if (properties != null) { foreach (var prop in properties) { var obj = prop.GetValue(_result, null); var enumeration = obj as IEnumerable; if (obj is IEnumerable) { foreach (var item in enumeration) { ProcessOnDeserialize(item); } } else { ProcessOnDeserialize(obj); } } } } 

这允许使用标准[OnDeserialized]

UPD。 更新了对象树上的递归遍历的post。

我使用一个工厂方法,在反序列化XML结构化对象后添加更多逻辑。 这种逻辑包括恢复对象成员之间的内部关系(子父,兄弟……)。

在我的情况下,它是一个对象的集合,所以使用一个例外的解决方案,必须稍微修改它

  private static void PostDeserializedProcess(T deserializedObj) { var deserializedCallback = deserializedObj as IXmlPostDeserializationCallback; if (deserializedCallback != null) { deserializedCallback.OnXmlDeserialized(deserializedObj); } else { // it could be a List of objects // and we need to check for every object in the list var collection = deserializedObj as System.Collections.IEnumerable; if (collection != null) { foreach (var item in collection) { PostDeserializedProcess(item); } } } } 

然后一切都很完美

我努力工作以获得上述解决方案。 我找到了最简单的解决方案,让我的OnDeserialization()回调激发,而使用XmlSerializer后来将链接调用BinaryFormatter。 我的类已经有了一个GetClone()方法,所以它很简单,并且否定了我重写XmlSerializer的所有尝试

 public static Foo Deserialize(string path) { Foo foo; XmlSerializer xmlSerializer = new XmlSerializer(typeof(Foo)); using (StreamReader textReader = new StreamReader(path)) { foo = (Foo)xmlSerializer.Deserialize(textReader); // this does NOT fire the OnDeserialization callbacks } return foo.GetClone(); } public Foo GetClone() { using (var ms = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(ms, this); ms.Position = 0; return (Foo)formatter.Deserialize(ms); // this DOES fire the OnDeserialization callbacks } } 

接受的解决方案对我来说也不起作用。

为了使它最终起作用,我需要稍微修改HotN的解决方案。 特别是我加了

 propertyInfo.GetIndexParameters().Length 

检查(根据: https : //docs.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo.getvalue ),以避免错配参数exception。

此外,我有一些不应该映射的属性。 我将它们归结为[XmlIgnore],但提供的解决方案仍然处理它们。 但是添加一个检查,检查传递的参数对象是否为空。

 namespace Custom.Xml.Serialization { public interface IXmlDeserializationCallback { void OnXmlDeserialization(object sender); } public class CustomXmlSerializer : XmlSerializer { public CustomXmlSerializer(Type type) : base(type) { } public new object Deserialize(Stream stream) { var result = base.Deserialize(stream); CheckForDeserializationCallbacks(result); return result; } private void CheckForDeserializationCallbacks(object deserializedObject) { if (deserializedObject == null) return; var deserializationCallback = deserializedObject as IXmlDeserializationCallback; if (deserializationCallback != null) { deserializationCallback.OnXmlDeserialization(this); } var properties = deserializedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var propertyInfo in properties) { if (propertyInfo.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null) { var collection = propertyInfo.GetValue(deserializedObject) as IEnumerable; if (collection != null) { foreach (var item in collection) { CheckForDeserializationCallbacks(item); } } } else { if (propertyInfo.GetIndexParameters().Length == 0) CheckForDeserializationCallbacks(propertyInfo.GetValue(deserializedObject)); } } } } }