【问题标题】:Unable to make ShouldSerialize pattern work with XmlSerializer无法使 ShouldSerialize 模式与 XmlSerializer 一起使用
【发布时间】:2015-12-01 16:08:49
【问题描述】:

我已经能够使用带有 XmlSerializer 的 ShouldSerializeProperty 模式来忽略 ICollection<> 类型的属性。下面是示例代码。如果我在ListOfTestClassB 属性上使用XmlIgnore,它可以工作,但我想使用ShouldSerialize 模式实现相同的功能。如果我运行下面的代码,我会得到“System.InvalidOperationException”:

无法序列化 System.Collections.Generic.ICollection`1[[ConsoleApplication3.TestClassB, ConsoleApplication3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] 类型的成员 ConsoleApplication3.TestClassA.ListOfTestClassB,因为它是一个接口.

谁能强调我错过了什么?

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace ConsoleApplication3
{
   [Serializable]
   public class TestClassA
   {
      public int A { get; set; }
      public int B { get; set; }
      public string C { get; set; }
      public TestClassB TestClass { get; set; }

      public virtual ICollection<TestClassB> ListOfTestClassB { get; set; }

      public bool ShouldSerializeListOfTestClassB()
      {
         return false;
      }
   }

   [Serializable]
   public class TestClassB 
   {
      public int Int32 { get; set; }
      public string String { get; set; }
   }


   class Program
   {
      static object GetObject()
      {
         return new TestClassA { A = 1, B = 2, C = "test class a", TestClass = new TestClassB { Int32 = 11, String = "test class b"} };
      }
      static void Main(string[] args)
      {
         var result = new StringBuilder();
         var entity = GetObject();
         var ser = new XmlSerializer(entity.GetType());

         var settings = new XmlWriterSettings { OmitXmlDeclaration = true };

         using (var stream = new MemoryStream())
         {
            // make a copy of the entity - we do not want to serialize ZIP file !
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, entity);
            stream.Position = 0;
            entity = formatter.Deserialize(stream);
         }

         // serialize object
         ser.Serialize(XmlWriter.Create(result, settings), entity);

         Console.WriteLine(result.ToString());
         Console.ReadLine();
      }
   }
}

【问题讨论】:

    标签: c# .net xmlserializer


    【解决方案1】:

    这是检查发生的地方:http://referencesource.microsoft.com/#System.Xml/System/Xml/Serialization/Models.cs,249

    如您所见,在它已经确定要序列化的字段/属性之前,它不会调用ShouldSerialize* 方法。所以,你的ListOfTestClassB 必须是可序列化的,或者它必须用[XmlIgnore] 装饰。为了可序列化,您的属性必须是应用了[Serializable] 属性的具体类型。

    如果您无法修改类,有一个解决方法。 XmlSerializer.Serialize(...) 方法的重载之一接受 overrides 对象。我在下面创建了一个简单的示例:

    [Serializable]
    public class Foo
    {
        public IList<int> Numbers { get; set; }
        public string TheNumber { get; set; }
    }
    
    class Program
    {
        private static void Main(string[] args)
        {
            var attributes = new XmlAttributes
            {
                XmlIgnore = true
            };
            var overrides = new XmlAttributeOverrides();
            overrides.Add(typeof(Foo), "Numbers", attributes);
    
            var serializer = new XmlSerializer(typeof(Foo), overrides);
    
            // the rest of this is for demo purposes.  
            // the code above is whats important
            //
            using (var ms = new MemoryStream())
            using (var reader = new StreamReader(ms))
            {
                serializer.Serialize(ms, new Foo() { TheNumber = "5" });    
                ms.Flush();
                ms.Seek(0, SeekOrigin.Begin);
                Debug.WriteLine(reader.ReadToEnd());
            }
    
        }
    
    }
    

    这会生成:

    <?xml version="1.0"?>
    <Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <TheNumber>5</TheNumber>
    </Foo>
    

    如您所见,我将XmlIgnore 属性动态添加到我的Numbers 元素中,因此序列化程序会忽略它。 :) 将其适应您自己的代码应该没有问题。

    注意: 正如dbc 所指出的,缓存此序列化程序并重新使用它很重要,否则您将遇到大量内存泄漏。您可以保留对它的静态引用,或者使用哈希表来存储不同类型的不同序列化程序。

    为了提高性能,XML 序列化基础结构动态生成程序集以序列化和反序列化指定类型。基础结构查找并重用这些程序集。此行为仅在使用以下构造函数时发生:

    XmlSerializer.XmlSerializer(Type)
    XmlSerializer.XmlSerializer(Type, String)
    

    如果您使用任何其他构造函数,则会生成同一程序集的多个版本并且永远不会卸载,这会导致内存泄漏和性能下降。最简单的解决方案是使用前面提到的两个构造函数之一。否则,您必须将程序集缓存在 Hashtable 中,如下例所示。

    【讨论】:

    • 那个序列化器需要被缓存,例如在静态哈希表中,以避免严重的内存泄漏。见XmlSerializer Class: Dynamically Generated Assemblies
    • 谢谢@dbc。我已经编辑了我的答案以引起人们的注意。
    • 感谢@Amy,这看起来很有希望。我遇到了 XmlAttributedOverride 的使用,但 ShouldSerialize 模式似乎是一个简单的实现,并且提供了更多的控制,但显然我不能使用它。我会使用你的建议。非常感谢!
    【解决方案2】:

    如果你使用XmlIgnore,那么它根本不会关心那个属性。如果您使用ShouldSerialize,它直到运行时才知道是否应该序列化该类型,因此它必须能够。在这种情况下,您尝试序列化的类型必须是具体类。尝试使用List&lt;TestClassB&gt;

    【讨论】:

    • 我无法控制班级。我将此示例编码为在此处发布。 TestClassA 是 EF6 数据库优先方法生成的示例。问题是接口不可序列化,因此会引发异常。在这种情况下我可以使用任何解决方法吗?我只是希望能够在运行时忽略这个 ICollection 属性。
    • 你对这个类没有任何控制权,但是你可以给它添加一个ShouldSerialize*方法?我很困惑。
    • @shigarr 查看艾米的评论
    • @Amy 我正在考虑使用部分类来添加该功能。
    猜你喜欢
    • 1970-01-01
    • 2011-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多