无法使 ShouldSerialize 模式与 XmlSerializer 一起使用

Unable to make ShouldSerialize pattern work with XmlSerializer

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

Cannot serialize member ConsoleApplication3.TestClassA.ListOfTestClassB of type System.Collections.Generic.ICollection`1[[ConsoleApplication3.TestClassB, ConsoleApplication3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface.

谁能强调我遗漏了什么?

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

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

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

如您所见,它在识别 fields/properties 之后才调用 ShouldSerialize* 方法进行序列化。所以,你的 ListOfTestClassB 必须是可序列化的, 必须用 [XmlIgnore] 装饰。为了可序列化,您的 属性 必须是应用了 [Serializable] 属性的具体类型。

如果您无法修改 class,则有一个解决方法。 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 所指出的,缓存这个序列化程序并重新使用它很重要,否则你将有很多内存泄漏.您可以保留对它的静态引用,或者使用哈希表来存储不同类型的不同序列化器。

To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:

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

If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.