¿Cómo se puede saber cuándo se ha cargado a través de Serialización XML?

Estoy intentando cargar un árbol de objetos a través de la serialización de XML, y en este momento cargará los objetos y creará el árbol con bastante alegría. Mi problema gira en torno al hecho de que estas clases admiten un nivel de auditoría. Lo que me gustaría hacer es llamar a algún método en cada objeto una vez que se haya cargado.

Por el bien del argumento, supongamos que tengo un árbol de objetos bastante genérico con diferentes clases en diferentes niveles, como:

  123 Any Street        456 High Street        

¿Hay alguna forma de utilizar los serializadores predeterminados (de la misma manera que puede crear métodos como ShouldSerializeFoo ) para determinar cuándo ha finalizado la carga de cada objeto?

Editar: Debo señalar que el caso obvio de exponer algo parecido a un método OnLoaded() que podría llamar después de la deserialización, me parece algo “malo”.

Edit2: en aras de la discusión, este es mi “enfoque” de hack actual, que funciona para el nivel básico, pero el nodo Child City todavía piensa que necesita guardarse con cambios (en el mundo real, el modelo de objetos es mucho más complejo) , pero al menos comstackrá, sin la necesidad de una fuente completa)

 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; } } 

Hmmm … todavía no es bonito, pero podrías refactorizar tu lógica de deserialización en una clase dedicada que podría notificar al objeto deserializado que se originó en XML antes de devolverlo a la persona que llama.

Actualización: creo que esto debería ser bastante fácil de hacer sin alejarse demasiado de los patrones establecidos por el marco … solo necesita asegurarse de usar CustomXmlSerializer. Las clases que necesitan esta notificación solo necesitan implementar 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; } } } 

La solución aceptada no funcionó para mí. El método Deserialize() anulado nunca se llamó. Creo que esto se debe a que ese método no es público y, por lo tanto, lo llaman uno (o más) de los Deserialize() públicos Deserialize() , pero no todos.

Aquí hay una implementación que funciona ocultando y haciendo uso de la interfaz IDeserializationCallback existente, por lo que cualquier deserialización que use métodos que no sean xml aún puede desencadenar el método OnDeserialization() de esa interfaz. También utiliza la reflexión para recorrer propiedades secundarias para ver si también implementa IDeserializationCallback y las llama en consecuencia.

 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)); } } } } } 

Probé la solución provista por abatishchev, pero como se señala en los comentarios debajo de su respuesta, el método Deserialize en el serializador personalizado nunca parece ser llamado.

Pude hacer que funcionara al sobrecargar todas las sobrecargas de Deserialize que necesitaría para llamar siempre al método personalizado.

 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); } } 

De esta manera, realmente veo que se Deserialize método Deserialize .

Un problema, ya que XmlSerializer no admite eventos de callback de serialización. ¿Hay alguna forma de que puedas usar DataContractSerializer ? Eso , pero no permite atributos (como @name arriba).

De otra manera; podrías implementar IXmlSerializable , pero eso es mucho trabajo y muy propenso a errores.

De lo contrario, quizás verificando a la persona que llama a través de la stack, pero eso es muy frágil y huele a humedad.

Después de perder algo de tiempo con la primera respuesta, adopté el código de la publicación de HotN, a excepción de 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); } } } } 

Esto permite usar el estándar [OnDeserialized] .

UPD. Puesto actualizado para caminata recursiva en el árbol de objetos.

Utilizo un método de fábrica que agrega más lógica después de que el objeto estructurado XML ha sido deserializado. Dicha lógica incluye restaurar la relación interna (hijo-padre, hermano …) entre los miembros del objeto.

En mi caso, se trataba de una colección de objetos, por lo que utilicé una solución exceptuada que tuve que modificar un poco

  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); } } } } 

Y luego todo está funcionando perfectamente

También luché un poco para lograr que las soluciones anteriores funcionaran. Encontré la solución más simple para que mis OnDeserialization() dispararan mientras usaba XmlSerializer para encadenar una llamada a BinaryFormatter después. Mi clase ya tenía un método GetClone() por lo que era bastante sencillo y anuló todos mis bashs de anular 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 } } 

La solución aceptada tampoco funcionó para mí.

Para que finalmente funcionara, necesité modificar un poco la solución de HotN. En particular, agregué el

 propertyInfo.GetIndexParameters().Length 

check (según: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo.getvalue ), para evitar una excepción de parámetros desajustados.

También tengo algunas propiedades que no deberían mapearse. Los atribuí con [XmlIgnore], pero la solución provista los procesó. Pero agregar una verificación de verificación para ver si el parámetro pasado es nulo fue el truco.

 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)); } } } } }