65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 sharpSerializer .NET 实现泛型字典、多维数组和继承类型的 XML 序列化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (47投票s)

2010 年 4 月 28 日

CPOL

8分钟阅读

viewsIcon

264795

downloadIcon

2910

如何序列化 XML、任何泛型、派生或其他 XMLSerializer 无法序列化的复杂类型

目录

引言

sharpSerializer 是一个适用于 .NET Framework、.NET Compact Framework 和 Silverlight 的开源对象序列化器。它的目的很简单——将对象快速地从 A 传输到 B,无需考虑安全性。在本文中,我将尝试说服您,sharpSerializer比内置的 XMLSerializer 功能更强大且更简单

背景

内置的 XmlSerializer 在对象序列化方面存在一些限制

  • 它无法序列化多维数组。
  • 它无法序列化泛型 Dictionary<TKey,TValue>
  • 它无法直接序列化多态属性 (属性值继承自属性类型)。
  • 它需要在其 Serialize(...) 方法中指定被序列化对象的类型。
  • 它需要大量属性来定义业务对象,例如:XmlArrayAttributeXmlArrayItemAttribute 来定义继承类型的数组。

sharpSerializer 克服了上述限制,并且在序列化对象时无需用额外的属性标记您的对象,也无需关心被序列化的类型。

Using the Code

使用 sharpSerializer 的 HelloWorld

假设我们有一个对象

public class SomeObject
{
    public int SimpleInt { get; set; }
    public DateTime SimpleDateTime { get; set; }
    public TimeSpan SimpleTimeSpan { get; set; }
    public SimpleEnum SimpleEnum { get; set; }
    public string SimpleString { get; set; }
}

public enum SimpleEnum {One,Two,Three}

我们初始化并序列化这个对象

// instantiate the object
var obj = new SomeObject(){
                      SimpleDateTime = new DateTime(2010,4,28),
                      SimpleEnum = SimpleEnum.Three,
                      SimpleInt = 42, 
                      SimpleString = "nothing",
                      SimpleTimeSpan = new TimeSpan(1,2,3)};

// instantiate the sharpSerializer
// with standard constructor it serializes to XML
// overloaded constructors activate binary serialization
var serializer = new SharpSerializer();

// serialize
serializer.Serialize(obj, "test.xml");

// deserialize (to check the serialization)
var obj2 = serializer.Deserialize("test.xml");

注意!您无需用任何属性标记您的对象,也无需在 Serialize 方法中提供对象类型。

此对象将被序列化为以下 XML

<Complex name="Root" type="SharpSerializerTestApp.SomeObject, SharpSerializerTestApp">
  <Properties>
    <Simple name="SimpleInt" value="42" />
    <Simple name="SimpleDateTime" value="04/28/2010 00:00:00" />
    <Simple name="SimpleTimeSpan" value="01:02:03" />
    <Simple name="SimpleEnum" value="Three" />
    <Simple name="SimpleString" value="nothing" />
  </Properties>
</Complex>

如您所见,对象类型被序列化为“TypeName, AssemblyName”,DateTime 被序列化为 CultureInfo.InvariantCulture。稍后,我将向您展示如何自定义类型名称 (例如,作为 AssemblyQualifiedName) 以及如何以您想要的格式保存 DateTimefloat 数字。

排除属性不进行序列化

默认情况下,所有 publicinstance 且非只读的属性都会被序列化。所有是数组 (Type.IsArray==true) 或继承自 IEnumerableICollectionIDictionary 的属性也会被序列化。出于性能原因,字段不会被序列化

  1. 要排除自定义类型中的属性,您需要用属性标记它们。ExcludeFromSerializationAttribute 是开箱即用的支持。
    public class MyClass
    {
        [ExcludeFromSerialization]
        public int SimpleInt { get; set; }
    }
  2. 如果您的对象是用常见的 .NET 属性标记的,例如 XmlIgnore,您可以将这些属性添加到 AttributesToIgnore 列表中。
    // remove default ExcludeFromSerializationAttribute for performance gain
    serializer.PropertyProvider.AttributesToIgnore.Clear(); 
    serializer.PropertyProvider.AttributesToIgnore.Add(typeof(XmlIgnore));

    或使用 settings

    // for binary mode
    var settings = new SharpSerializerBinarySettings();
    // for xml mode
    var settings = new SharpSerializerXmlSettings(); 
    // remove default ExcludeFromSerializationAttribute for performance gain
    settings.AdvancedSettings.AttributesToIgnore.Clear(); 
    settings.AdvancedSettings.AttributesToIgnore.Add(typeof(XmlIgnore));
  3. 要排除内置 .NET 类型中的属性,或者如果您无法用属性扩展属性,可以将类型及其属性名称添加到 SharpSerializer.PropertyProvider.PropertiesToIgnore 列表中。

    例如,System.Collections.Generic.List<T> 有一个 Capacity 属性,这对序列化来说是不相关的,因此应该被忽略。
    serializer.PropertyProvider.PropertiesToIgnore.Add
    	(typeof(List<string>), "Capacity");

    也可以从 sharpSerializer 的设置中访问 PropertiesToIgnore

    // create the settings
    var settings = new SharpSerializerBinarySettings(); // for binary mode
    var settings = new SharpSerializerXmlSettings(); // for xml mode
    settings.AdvancedSettings.PropertiesToIgnore.Add(typeof(List), "Capacity");

自定义用于序列化的属性列表

如果仅使用 ExcludeFromSerializationAttributeAttributesToIgnorePropertiesToIgnore 过滤属性还不够,您可以构建自己的 CustomPropertyProvider。有一个基类 PropertyProvider,它有两个虚拟方法 GetAllProperties()IgnoreProperty(...)。可以重写它们来自定义逻辑。

serializer.PropertyProvider = new MyCustomPropertyProvider();

将类型序列化为 AssemblyQualifiedName 或简短类型名称“TypeName, AssemblyName”

在 SharpSerializer v.2.12 之前,所有类型都序列化为简短类型名称“TypeName, AssemblyName”;例如:

type="System.String, mscorlib"

这样阅读简单,输出大小也小。但在处理签名程序集或其特定版本时,反序列化会出现问题。

自 SharpSerializer v.2.12 起,所有类型都序列化为 AssemblyQualifiedName;例如:

type="System.String, mscorlib, Version=2.0.0.0, 
      Culture=neutral, PublicKeyToken=b77a5c561934e089"

您可以通过修改 sharpSerializer 的设置并设置 IncludeAssemblyVersionInTypeNameIncludeCultureInTypeNameIncludePublicKeyTokenInTypeName 属性来更改类型命名。默认情况下,这些属性设置为 true

var settings = new SharpSerializerXmlSettings();

settings.IncludeAssemblyVersionInTypeName = false;
settings.IncludeCultureInTypeName = false;
settings.IncludePublicKeyTokenInTypeName = false;

var serializer = new SharpSerializer(settings);

自定义 DateTime 和 Float 值的格式

默认情况下,所有基本类型和 DateTime 都根据 CultureInfo.InvariantCulture 转换为 string。如果需要自定义格式或区域设置,可以通过修改设置类来实现:

var settings = new SharpSerializerXmlSettings();
settings.Culture = System.Globalization.CultureInfo.CurrentCulture;
var serializer = new SharpSerializer(settings);

将数据序列化为 XML 以外的其他格式

实际上,sharpSerializer 可以序列化为 XML 和它自己的二进制格式。但是,通过注入自定义的 IXmlWriterIBinaryWriter,它可以将数据序列化为其他文本格式,如 Json,或其他加密、压缩、优化等二进制流。有关二进制序列化的详细信息,请参阅项目站点

sharpSerializer 如何工作?

它分三个步骤工作

步骤 1

PropertyFactory 将对象转换为 PropertyabstractProperty 包含 PropertyCollectionPropertyCollection 代表对象结构及其数据。以下类继承自 Property

  • SimpleProperty 用于描述所有基本类型和 stringDateTimeTimeSpan 以及所有枚举
  • ComplexProperty 用于非列表类的其他类和结构
  • ComplexReferenceProperty 用于引用已被序列化的类 (可以节省空间)
  • SingleDimensionalArrayProperty 用于一维数组
  • MultiDimensionalArrayProperty 用于多维数组
  • CollectionProperty 用于继承自 ICollection 但不继承自 IDictionary 的列表
  • DictionaryProperty 用于继承自 IDictionary 的列表
  • NullProperty 用于所有为 null 的对象/字符串 (需要注意它们是 null)

第二步

PropertyXmlPropertySerializer 序列化。序列化的格式取决于使用的写入器。

步骤 3

对于 XML 序列化,由 DefaultXmlWriter 负责。但作为接收端,可以使用任何实现了 IXmlWriter 的写入器。这样,您可以编写自己的写入器,将数据序列化为 Json 等其他格式。

在反序列化过程中,过程是相反的。IXmlReader 的一个实例读取数据并将其转发给 XmlPropertyDeserializer,后者将数据反序列化为 Property。最后,ObjectFactoryProperty 转换为对象。

请下载源代码以获取更多详细信息。

高级序列化示例

以下示例是 HelloWorldDemo 应用程序的一部分,您可以与源代码一起下载。

序列化多态属性 (属性值继承自属性类型)

有一个类型为 IComplexObject 的属性。该属性值包含一个继承自 IComplexObject 的类 ComplexObject

public IComplexObject ComplexObject { get; set; }

无需输入要序列化的类型。sharpSerializer 可以开箱即用地序列化预期的类型和任何继承类型。

<Complex name="ComplexObject" 
     type="HalloWorldApp.BusinessObjects.ComplexObject, HalloWorldApp">
  <Properties>
    <Simple name="SimpleInt" value="33" />
  </Properties>
</Complex>

序列化泛型 Dictionary,其值继承自接口

有一个泛型字典,其值是继承自接口 IComplexObject 的某个复杂类 (多态参数)

public IDictionary<int, IComplexObject> GenericDictionary { get; set; }

它被序列化为

<Dictionary name="GenericDictionaryOfPolymorphicValues" 
          type="System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],
                   [HalloWorldApp.BusinessObjects.IComplexObject, HalloWorldApp]], 
                   mscorlib" keyType="System.Int32, mscorlib" 
          valueType="HalloWorldApp.BusinessObjects.IComplexObject, HalloWorldApp">
    <Items>
      <Item>
        <Simple value="2012" />
        <Complex type="HalloWorldApp.BusinessObjects.ComplexObject, HalloWorldApp">
          <Properties>
            <Simple name="SimpleInt" value="2012000" />
          </Properties>
        </Complex>
      </Item>
    </Items>
</Dictionary>

如您所见,键和值的类型无关紧要。sharpSerializer 可以序列化基本类型、复杂对象,甚至嵌套列表,例如字典。

序列化多维数组

这是一个二维数组 (可以有更多维度)

public string[,] DoubleArray { get; set; }

它被序列化为

<MultiArray name="DoubleArray" elementType="System.String, mscorlib">
    <Dimensions>
      <Dimension length="3" />
      <Dimension length="2" />
    </Dimensions>
    <Items>
      <Item indexes="0,0">
        <Simple value="k1" />
      </Item>
      <Item indexes="0,1">
        <Simple value="k2" />
      </Item>
      <Item indexes="1,0">
        <Simple value="b1" />
      </Item>
      <Item indexes="1,1">
        <Simple value="b2" />
      </Item>
      <Item indexes="2,0">
        <Simple value="z1" />
      </Item>
      <Item indexes="2,1">
        <Simple value="z2" />
      </Item>
    </Items>
</MultiArray>

数组的类型是什么并不重要。sharpSerializer 可以序列化任何对象的数组、数组的数组、集合的数组或字典的数组。它可以序列化真正深度嵌套的数组。

序列化不同对象类型的数组和数组的数组 (嵌套数组)

这是一个包含以下对象的数组

root.SingleArrayOfObjects = new object[] 
{
    42, 
    "nothing to say", 
    false, 
    BusinessObjects.SimpleEnum.Three, 
    null, 
    new object[] 
           {
              42, 
              "nothing to say", 
              false, 
              BusinessObjects.SimpleEnum.Three, 
              null 
           } 
};

它被序列化为

<SingleArray name="SingleArrayOfObjects" elementType="System.Object, mscorlib">
    <Items>
      <Simple type="System.Int32, mscorlib" value="42" />
      <Simple type="System.String, mscorlib" value="nothing to say" />
      <Simple type="System.Boolean, mscorlib" value="False" />
      <Simple type="HalloWorldApp.BusinessObjects.SimpleEnum, 
		HalloWorldApp" value="Three" />
      <Null />
      <SingleArray type="System.Object[], mscorlib" 
		elementType="System.Object, mscorlib">
        <Items>
          <Simple type="System.Int32, mscorlib" value="42" />
          <Simple type="System.String, mscorlib" value="nothing to say" />
          <Simple type="System.Boolean, mscorlib" value="False" />
          <Simple type="HalloWorldApp.BusinessObjects.SimpleEnum, HalloWorldApp" 
        value="Three" />
          <Null />
        </Items>
      </SingleArray>
    </Items>
</SingleArray>

序列化其他类型

请下载源代码以查看完整的示例以及还可以序列化哪些内容。以下是 HelloWorldDemo 中的一些其他属性

public class RootContainer
{
    /// <summary>
    /// Structures are handled as objects during serialization
    /// They are serialized as ComplexProperty
    /// </summary>
    public AdvancedStruct AdvancedStruct { get; set; }

    /// <summary>
    /// Single dimensional array of simple type.
    /// It is serialized as SingleDimensionalArrayProperty
    /// </summary>
    public string[] SingleArray { get; set; }

    /// <summary>
    /// Multidimensional array of simple type.
    /// Is is serialized as MultiDimensionalArrayProperty
    /// </summary>
    public string[,] DoubleArray { get; set; }

    /// <summary>
    /// Single array of derived objects.
    /// This is polymorphic collection - Items derive from the interface
    /// </summary>
    public IComplexObject[] PolymorphicSingleArray { get; set; }

    /// <summary>
    /// Generic list is serialized as a collection.
    /// It is serialized as CollectionProperty
    /// </summary>
    public IList<string> GenericList { get; set; }

    /// <summary>
    /// Polymorphic property. Object instance derives from the property type
    /// Is serialized as ComplexProperty
    /// </summary>
    public IComplexObject ComplexObject { get; set; }

    /// <summary>
    /// Collection where item values are
    /// derived from the collection item type
    /// </summary>
    public ComplexObjectPolymorphicCollection ComplexObjectCollection
                { get; set; }

    /// <summary>
    /// Dictionary where values are derived
    /// from the predefined dictionary value type
    /// </summary>
    public ComplexObjectPolymorphicDictionary ComplexObjectDictionary 
                { get; set; }

    /// <summary>
    /// List items are derived from the generic attribute.
    /// This is polymorphic attribute.
    /// </summary>
    public IList<IComplexObject> GenericListOfComplexObjects { get; set; }

    /// <summary>
    /// Generic object with polymorphic attribute.
    /// It is serialized as ComplexProperty
    /// </summary>
    public GenericObject<IComplexObject> GenericObjectOfComplexObject
                { get; set; }

    /// <summary>
    /// Multidimensional array of generic object with polymorphic attribute
    /// </summary>
    public GenericObject<IComplexObject>[,] 
	MultiArrayOfGenericObjectWithPolymorphicArgument
                { get; set; }
}

public interface IComplexObject { int SimpleInt { get; set; } }

public class ComplexObject : IComplexObject {public int SimpleInt
                { get; set; }}

public class ComplexObjectPolymorphicCollection :
                Collection<IComplexObject>{}

public class ComplexObjectCollection :
                Collection<ComplexObject>{}

public class ComplexObjectPolymorphicDictionary :
                Dictionary<int, IComplexObject>{}

public class ComplexObjectDictionary :
                Dictionary<int, ComplexObject>{}

您认为呢?使用 sharpSerializer 进行 XML 序列化是否足够简单?

sharpSerializer 的限制

sharpSerializer 的当前版本中,在序列化和反序列化对象方面存在一些限制。将来,这些限制可能会被消除,但目前它们并没有带来太大的麻烦。它们是:

  • 没有 public 标准构造函数的对象无法被反序列化。
  • 对同一复杂对象的多个引用没有进行优化。此类多个引用的对象会被序列化多次,与引用次数相同。(.NET 2.9 及以上版本 SharpSerializer 可以优化对同一对象的多个引用的序列化。此类对象只会被序列化一次。这种优化会减小文件大小。)

以下限制适用于 .NET Compact Framework 和 Silverlight 中的序列化

  • 数组中的 LowerBound 将始终反序列化为 0,无论它被如何序列化。

数组的 LowerBound 不是 .NET Compact Framework 或 Silverlight 的一部分,因此无法由 sharpSerializer 处理。

使用场景

我开发 sharpSerializer 的主要原因是将应用程序配置保存在 XML 文件中。几年前,我为多态配置而苦恼。我需要一个轻量级的配置存储,支持对象继承。文件应该是易于阅读和手动编辑的。System.Configuration 的开销很大,而且太僵化。

第二个原因是简单——Silverlight 对本地数据存储的支持很差——它应该有一个更好的!:-)
其在 WP7 (Windows Phone 7) 中序列化数据到二进制格式的可能性尤其值得关注。

下载当前源代码

最新的源代码和新闻请访问项目页面:www.sharpserializer.com

作者笔记

如果您喜欢 sharpSerializer 或本文——请给它评分。如果不喜欢,请在下方留言 ;-)

历史

  • 2011-11-09:根据 sharpSerializer v.2.16 的更改
  • 2011-10-24:更新了下载文件
  • 2011-07-31:更新了下载文件 SharpSerializer v.2.12
  • 2011-07-28:更新了下载文件 sharpSerializer v.2.11
  • 2011-05-08:根据 sharpSerializer v.2.9、Guid 序列化、AttributesToIgnore 和对同一对象的多个引用的优化序列化进行的更改
  • 2010-10-07:更新了下载文件
  • 2010-10-03:根据 sharpSerializer v.2.0 进行的更改
  • 2010-05-05:根据 sharpSerializer v.1.2 进行的更改
  • 2010-05-04:增加了对 .NET Compact Framework 的支持
  • 2010-04-30:格式更改 (删除了大型 XML 示例的一些部分)
  • 2010-04-29:首次发布
© . All rights reserved.