【问题标题】:How do you find out when you've been loaded via XML Serialization?您如何知道您何时已通过 XML 序列化加载?
【发布时间】:2010-11-18 23:43:18
【问题描述】:

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

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

 <Customer name="Foo Bar Inc.">
   <Office IsHq="True">
     <Street>123 Any Street</Street>
     <Town name="Anytown">
       <State name="Anystate">
         <Country name="My Country" />
       </State>
     </Town>
   </Office>
   <Office IsHq="False">
     <Street>456 High Street</Street>
     <Town name="Anycity">
       <State name="Anystate">
         <Country name="My Country" />
       </State>
     </Town>
   </Office>
 </Customer>

有没有什么方法可以使用默认的序列化程序(类似于您可以创建 ShouldSerializeFoo 等方法)来确定每个对象的加载何时完成?

编辑: 我应该指出,在反序列化后公开类似于我可以调用的OnLoaded() 方法的明显案例,让我觉得“做坏事”。

编辑2: 为了讨论,这是我目前的 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 = @"
                    <Office IsHq='True'>
                        <Street>123 Any Street</Street>
                        <Town name='Anytown'>
                            <State name='Anystate'>
                                <Country name='My Country' />
                            </State>
                        </Town>
                    </Office>";

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

【问题讨论】:

  • 不是一个真正的答案,但你为什么不能在属性获取访问器中进行审计?
  • 审计在属性集中,所以它在内部保留了一个记录,说明它自加载以来已更改(因此需要保存,然后会说所有值都从默认值更改为加载的实际值)
  • 嗯,在这种情况下,我们就应该如何做达成一致,这让人放心:)

标签: c# .net serialization xml-serialization


【解决方案1】:

嗯...它仍然不漂亮,但您可以将反序列化逻辑重构为一个专用类,该类可以在将反序列化对象返回给调用者之前通知它源自 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;
        }
    }
}

【讨论】:

  • 我认为这个选项最有意义。反序列化后对对象进行后处理。
  • 玩弄它我仍然认为它有点笨拙,但如果处理反序列化的类使用泛型并通过接口通知反序列化的对象,那么它会更好一点,而且编码也不会太糟糕。
  • 我正试图让它工作并且反序列化覆盖永远不会被调用。我是否缺少其他部分?
  • 是的,这对我也不起作用。永远不会调用 CustomXmlSerializer.Deserialize。我觉得这个答案应该被修改,除非有人能解释我们中的一些人做错了什么。否则这只是让人们抱有希望,然后在它不起作用时浪费他们的时间。我正在使用 .NET 4.0。
  • @eodabash 它对我有用——也许你应该问一个新问题来解释你尝试了什么,发生了什么而不是你期望的。
【解决方案2】:

接受的解决方案对我来说不太适用。被覆盖的 Deserialize() 方法从未被调用。我相信这是因为该方法不是公共的,因此被一个(或多个)公共 Deserialize() 方法调用,但不是全部。

这是一个通过隐藏方法工作并利用现有IDeserializationCallback 接口的实现,因此任何使用非xml 方法的反序列化仍然可以触发该接口的OnDeserialization() 方法。它还使用反射来遍历子属性,看看它们是否也实现了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));
                }
            }
        }
    }
}

【讨论】:

  • 它是否对嵌套属性的对象递归工作?
  • 应该,是的。请参阅CheckForDeserializationCallbacks() 方法以检查属性对象的递归调用。
【解决方案3】:

我尝试了 abatishchev 提供的解决方案,但正如他的回答下方的 cmets 所指出的,自定义序列化程序中的 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 方法被调用了。

【讨论】:

  • 不幸的是,这对我也不起作用,使用 .Net 4.0 :-(
【解决方案4】:

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

否则;你可以实现IXmlSerializable,但这是很多的工作,而且很容易出错。

否则 - 可能会通过堆栈检查调用者,但这非常很脆弱,而且闻起来很成熟。

【讨论】:

    【解决方案5】:

    在第一个答案浪费了一些时间之后,我采用了 HotN 帖子中的代码,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。更新了关于递归遍历对象树的帖子。

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

          private static void PostDeserializedProcess<T>(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);
                        }
                    }
                }
            }
        

        然后一切正常

        【讨论】:

          【解决方案8】:

          我在让上述解决方案发挥作用时也有些挣扎。我发现在使用 XmlSerializer 时触发 OnDeserialization() 回调的最简单的解决方案是在之后链接对 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
              }
          }
          

          【讨论】:

            【解决方案9】:

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

            为了让它最终工作,我需要稍微修改一下 HotN 的解决方案。 特别是我添加了

            propertyInfo.GetIndexParameters().Length
            

            检查(根据:https://docs.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo.getvalue),以免出现参数不匹配异常。

            我还有一些不应该被映射的属性。我将它们归因于 [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));
                            }
                        }
                    }
                }
            }
            

            【讨论】:

              【解决方案10】:

              我还没有对此进行测试,但我想在 HotN 的答案的基础上支持OnSerializing 和OnSerialized。我不相信有对应的ISerializationCallback,所以我自己实现了。

              更新(1)

              我原来的解决方案有缺陷。以下是经过测试和工作的。

              更新(2)

              IDeserializationCallback 似乎并不总是有效,因此我将其删除。

              XmlCallbackSerializer

              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;
              
                  public 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);
                      EachWith<OnDeserializedAttribute>(result);
                      return result;
                  }
              
                  public new object Deserialize(TextReader textReader)
                  {
                      var result = base.Deserialize(textReader);
                      EachWith<OnDeserializedAttribute>(result);
                      return result;
                  }
              
                  public new object Deserialize(XmlReader xmlReader)
                  {
                      var result = base.Deserialize(xmlReader);
                      EachWith<OnDeserializedAttribute>(result);
                      return result;
                  }
              
                  public new object Deserialize(XmlSerializationReader reader)
                  {
                      var result = base.Deserialize(reader);
                      EachWith<OnDeserializedAttribute>(result);
                      return result;
                  }
              
                  public new object Deserialize(XmlReader xmlReader, string encodingStyle)
                  {
                      var result = base.Deserialize(xmlReader, encodingStyle);
                      EachWith<OnDeserializedAttribute>(result);
                      return result;
                  }
              
                  public new object Deserialize(XmlReader xmlReader, XmlDeserializationEvents events)
                  {
                      var result = base.Deserialize(xmlReader, events);
                      EachWith<OnDeserializedAttribute>(result);
                      return result;
                  }
              
                  public new object Deserialize(XmlReader xmlReader, string encodingStyle, XmlDeserializationEvents events)
                  {
                      var result = base.Deserialize(xmlReader, encodingStyle, events);
                      EachWith<OnDeserializedAttribute>(result);
                      return result;
                  }
              
                  //...
              
                  public new void Serialize(object o, XmlSerializationWriter xmlSerializationWriter)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(o, xmlSerializationWriter);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(Stream stream, object o)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(stream, o);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(TextWriter textWriter, object o)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(textWriter, o);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(XmlWriter xmlWriter, object o)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(xmlWriter, o);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(Stream stream, object o, XmlSerializerNamespaces namespaces)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(stream, o, namespaces);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(TextWriter textWriter, object o, XmlSerializerNamespaces namespaces)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(textWriter, o, namespaces);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(XmlWriter xmlWriter, object o, XmlSerializerNamespaces namespaces)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(xmlWriter, o, namespaces);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(XmlWriter xmlWriter, object o, XmlSerializerNamespaces namespaces, string encodingStyle)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(xmlWriter, o, namespaces, encodingStyle);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  public new void Serialize(XmlWriter xmlWriter, object o, XmlSerializerNamespaces namespaces, string encodingStyle, string id)
                  {
                      EachWith<OnSerializingAttribute>(o);
                      base.Serialize(xmlWriter, o, namespaces, encodingStyle, id);
                      EachWith<OnSerializedAttribute>(o);
                  }
              
                  //...
              
                  public static bool HasAttribute<T>(MemberInfo input)
                  {
                      foreach (var i in input.GetCustomAttributes(true))
                      {
                          if (i is T)
                              return true;
                      }
                      return false;
                  }
              
                  //...
              
                  bool IsValidType(Type i)
                      => i is not null 
                      && !i.Equals(typeof(string)) 
                      && !i.IsArray 
                      && i.IsClass 
                      && !i.IsEnum 
                      && !i.IsImport 
                      && !i.IsInterface 
                      && !i.IsPrimitive 
                      && i.IsPublic 
                      && i.IsSerializable 
                      && !i.IsValueType 
                      && i.IsVisible;
              
                  //...
              
                  void Each(object input, Action<object> action) 
                  {
                      var type = input?.GetType();
                      if (IsValidType(type))
                      {
                          action(input);
                          if (input is IEnumerable j)
                          {
                              foreach (var i in j)
                                  Each(i, action);
                          }
              
                          foreach (var i in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
                          {
                              if (IsValidType(i.PropertyType))
                              {
                                  object temp = default;
                                  Try.Invoke(() => temp = i.GetValue(input, null));
                                  if (temp != null)
                                      Each(temp, action);
                              }
                          }
                      }
                  }
              
                  void EachWith<T>(object input) where T : Attribute
                  {
                      Each(input, i =>
                      {
                          var methods = i.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                          if (methods?.Length > 0)
                          {
                              foreach (var j in methods)
                              {
                                  if (HasAttribute<T>(j))
                                      j.Invoke(i, new object[] { new StreamingContext(StreamingContextStates.Other) });
                              }
                          }
                      });
                  }
              }
              

              试试

              using System;
              
              public static class Try
              {
                  public static bool Invoke(Action @try, Action<Exception> @catch = null)
                  {
                      try
                      {
                          @try();
                          return true;
                      }
                      catch (Exception e)
                      {
                          @catch?.Invoke(e);
                          return false;
                      }
                  }
              }
              

              方法必须

              • 包括OnSerializing 或OnSerialized 属性
              • 并定义一个StreamingContext参数

              要与BinaryFormatter 互换使用,StreamingContext 参数是必需的。在 XML 序列化期间执行该方法时,会创建一个新的 StreamingContext 实例。

              例子:

              [OnDeserialized]
              internal/private/protected/public void OnDeserialized(StreamingContext context) { }
              
              [OnSerializing]
              internal/private/protected/public void OnSerializing(StreamingContext context) { }
              
              [OnSerialized]
              internal/private/protected/public void OnSerialized(StreamingContext context) { }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-01-03
                • 2018-12-05
                • 1970-01-01
                • 1970-01-01
                • 2016-09-25
                相关资源
                最近更新 更多