XmlSerializer 大法好

请原谅我之前的愚昧无知。我已经决定完全分离对象模型和对象的逻辑行为了。

引用使用 Id 保存,即类中不再出现对其他类的直接引用,而仅仅保存 Id。这样在序列化的时候就可以直接保存了。

XmlSerializer 是线程安全的。
XmlSerializer 是线程安全的。

然而我发现处理 Nullable 类型还是一个很麻烦的问题……
c# – Is there any way for my class to support serialisation as an XML attribute? – Stack Overflow

所以我要自己造轮子!

好吧……我决定自己实现 XmlSerializer 了

重新造轮子 TT

ref = CXuesong/XSerializer/

欢迎围观 TT

最近一直在考虑以下问题:

在使用 `XmlSerializer `以精确控制 XML 格式的同时

  1. 如何序列化 `Nullable<T>` 格式的数据,并将其保存为 XML 属性(而非元素)?
  2. 如何序列化对象的引用,使其不会以多个副本的形式保存?
  3. 在实现上面这两点(尤其是第一点)的时候,不会引入其他的公共辅助属性。
    (例如对于第一点,可以通过一个辅助属性,实现 `Nullable<T>` 与 `string` 之间的转换,而 `string` 是可以保存为 XML 属性的。)

其中,第二个问题似乎可以使用 `DataContractSeriallizer` 来解决。然而 `DataContractSeriallizer` 会在生成 XML 时自行为对象确定 Id,并使用 `z:Id` 和`z:Ref` 来表示 Id 和 Id 引用。正如在前几篇文章中提到的那样,`DataContractSeriallizer` 对 XML 结构的控制自由度是十分有限的。

那么,如何才能圆满地解决这些问题呢?

我觉得,可以自己造轮子了。

 

XmlSerializer 测试 2

集合

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var f = new XmlSerializer(typeof(TestClass));
            using (var sw = new StringWriter())
            {
                f.Serialize(sw, new TestClass());
                using (var sr = new StringReader(sw.ToString()))
                {
                    var bt = (TestClass) f.Deserialize(sr);
                    Console.WriteLine(bt.ListIdentical());
                }
            }
        }
    }

    public class TestClass
    {
        private List<int> PrivateList;
        public List<int> MyList;

        public bool ListIdentical()
        {
            return PrivateList == MyList;
        }

        public TestClass()
        {
            PrivateList = new List<int>() { 123, 567 };
            MyList = PrivateList;
        }
    }
}

运行结果为 `True` 。实际上,如果把 `MyList` 换成只读属性,只要其值非 null / Nothing,则程序仍可以正常运行。

也就是说,反序列化过程复用了已有的集合实例。 Continue reading “XmlSerializer 测试 2”

XmlSerializer 测试

测试源代码如下,使用 VS2013 编写。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var f = new XmlSerializer(typeof(TestClass), new[] { typeof(DerivedClass1), typeof(ChildNS.DerivedClass1) });
            var ns = new XmlSerializerNamespaces();
            ns.Add("ns", "http://yourcompany.org/schemas/ns");
            ns.Add("nsc", "http://yourcompany.org/schemas/ns/child");
            ns.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");
            using (var sw = new StringWriter())
            {
                f.Serialize(sw, new TestClass(), ns);
                Console.WriteLine(sw.ToString());
            }
        }
    }

    public class TestClass
    {
        public BaseClass BaseTest = new BaseClass();

        public BaseClass BaseTest1 = new DerivedClass1();

        [XmlElement(Namespace = "http://yourcompany.org/schemas/ns/child")]
        public BaseClass BaseTest2 = new ChildNS.DerivedClass1();

        public BaseClass[] BaseArray1 = {new BaseClass(), new DerivedClass1()};

        [XmlArrayItem(typeof(BaseClass), IsNullable = true),
         XmlArrayItem(typeof(DerivedClass1), IsNullable = true),
         XmlArrayItem(typeof(DerivedClass2), IsNullable = true)]
        public BaseClass[] BaseArray2 =
         {
             new BaseClass(), new DerivedClass1(),
             null, new DerivedClass2()
         };

        [XmlArrayItem(typeof(BaseClass), IsNullable = true)]
        public BaseClass[] BaseArray3 =
        {
            new BaseClass(),
            new DerivedClass1(), null, new DerivedClass2()
        };

        [XmlArrayItem(typeof(BaseClass))]
        public List<BaseClass> BaseList = new List<BaseClass> { new BaseClass(), new DerivedClass1() };

        [XmlElement(IsNullable = true)]
        public object NullObject1 = null;

        public object NullObject2 = null;

        //public IEnumerable BaseListEnumerable = new List<BaseClass> { new BaseClass(), new DerivedClass1() };
    }

    public class BaseClass
    {
        [XmlAttribute("value1", Namespace = "http://yourcompany.org/schemas/ns")]
        public int Value1;

        [XmlAttribute("value2")]
        public int Value2;

        [XmlAttribute("value3", Namespace = "http://yourcompany.org/schemas/ns")]
        public int Value3;
    }

    public class DerivedClass1 : BaseClass
    {
        [XmlAttribute("value4", Namespace = "http://yourcompany.org/schemas/ns")]
        public int Value4;
    }

    public class DerivedClass2 : BaseClass
    {
        [XmlAttribute("value4", Namespace = "http://yourcompany.org/schemas/ns")]
        public int Value4;
    }

    namespace ChildNS
    {
        //为类型强制指定 XML 名称,以及命名空间,以避免命名冲突。
        [XmlType("ChildDerivedClass", Namespace = "http://yourcompany.org/schemas/ns/child")]
        public class DerivedClass1 : BaseClass
        {
            public int Value5;
        }
    }
}

运行结果如下

<?xml version="1.0" encoding="utf-16"?>
<TestClass xmlns:ns="http://yourcompany.org/schemas/ns" xmlns:nsc="http://yourcompany.org/schemas/ns/child" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <BaseTest ns:value1="0" value2="0" ns:value3="0" />
  <BaseTest1 xsi:type="DerivedClass1" ns:value1="0" value2="0" ns:value3="0" ns:value4="0" />
  <nsc:BaseTest2 xsi:type="nsc:ChildDerivedClass" ns:value1="0" value2="0" ns:value3="0">
    <nsc:Value5>0</nsc:Value5>
  </nsc:BaseTest2>
  <BaseArray1>
    <BaseClass ns:value1="0" value2="0" ns:value3="0" />
    <BaseClass xsi:type="DerivedClass1" ns:value1="0" value2="0" ns:value3="0" ns:value4="0" />
  </BaseArray1>
  <BaseArray2>
    <BaseClass ns:value1="0" value2="0" ns:value3="0" />
    <DerivedClass1 ns:value1="0" value2="0" ns:value3="0" ns:value4="0" />
    <DerivedClass2 ns:value1="0" value2="0" ns:value3="0" ns:value4="0" />
  </BaseArray2>
  <BaseArray3>
    <BaseClass ns:value1="0" value2="0" ns:value3="0" />
    <BaseClass xsi:type="DerivedClass1" ns:value1="0" value2="0" ns:value3="0" ns:value4="0" />
    <BaseClass xsi:nil="true" />
    <BaseClass xsi:type="DerivedClass2" ns:value1="0" value2="0" ns:value3="0" ns:value4="0" />
  </BaseArray3>
  <BaseList>
    <BaseClass ns:value1="0" value2="0" ns:value3="0" />
    <BaseClass xsi:type="DerivedClass1" ns:value1="0" value2="0" ns:value3="0" ns:value4="0" />
  </BaseList>
  <NullObject1 xsi:nil="true" />
</TestClass>

可以看出,

  1. 在 `XmlArrayItem(Attribute)`、`XmlElement` 等相关特性中指定 IsNullable = true 可以使得当前为 null / Nothing 的元素在序列化时产生一个包含 xsi:nil=”true” 特性的 XML 元素。
  2. 在特性中指定元素的命名空间时,应当指定命名空间的 URI,尽管可以稍后在 `XmlSerializerNamespaces` 中为这些 URI 指定对应的命名空间前缀。
  3. 需要序列化派生类时,总是 需要通过 XmlInclude 特性,或者通过 `XmlSerializer` 的构造函数显式声明序列化过程中可能用到的派生类。
  4.  数组的序列化:可以通过 `XmlArrayItem` 特性声明数组中可能包含的派生类类型。这些类型在序列化时会使用与之对应的元素名。如果存在未在 `XmlArrayItem` 中声明的派生类,而其基类在 `XmlArrayItem` 中声明过,则会使用基类对应的元素名,外加 `xsi:type` 特性声明实际派生类的类型。(关于这一点,可以参阅另一篇文章:MSDN中“(使用 XmlArrayItemAttribute 限定)序列化派生类”一节中的示例可能与实际有出入
  5. 我该滚去修改以前的代码了。

MSDN中“(使用 XmlArrayItemAttribute 限定)序列化派生类”一节中的示例可能与实际有出入

cite=https://msdn.microsoft.com/ZH-CN/library/vstudio/2baksw0z.aspx

XmlArrayItemAttribute 的另一种用法是,允许序列化派生类。 例如,可将派生自 Employee 的另一个名为 Manager 的类添加至上一示例中。 如果没有应用 XmlArrayItemAttribute,代码将在运行时失败,原因是无法识别派生类类型。 若要解决这个问题,每次为每个可接受类型(基类和派生类)设置 Type 属性时,需要应用该特性两次。

public class Group{
    [XmlArrayItem(Type = typeof(Employee)),
    XmlArrayItem(Type = typeof(Manager))]
    public Employee[] Employees;
}
public class Employee{
    public string Name;
}
public class Manager:Employee{
    public int Level;
}

序列化实例可能如下所示。

<Group>
<Employees>
    <Employee>
        <Name>Haley</Name>
    </Employee>
    <Employee xsi:type = "Manager">
        <Name>Ann</Name>
        <Level>3</Level>
    <Employee>
</Employees>
</Group>

然而实际情况不一样…… Continue reading “MSDN中“(使用 XmlArrayItemAttribute 限定)序列化派生类”一节中的示例可能与实际有出入”

与语言标识相关的两个基本函数

今天回趟家,顺便看看以前的代码……

Imports System.Text
 
''' <summary> 
''' 与语言相关的实用工具。 
''' </summary> 
Public Class Locale 
    ''' <summary> 
    ''' 将指定的语言标识格式化为标准格式。 
    ''' </summary> 
    ''' <param name="languageTag">待格式化的符合 BCP47 或 UTS#35 要求的语言标识。</param> 
    ''' <returns>符合 BCP47(RFC5646) 建议(如大小写建议)的语言标识。如果 <paramref name="languageTag" /> 为 <c>null</c>,则返回空字符串。</returns> 
    Public Shared Function FormatTag(ByVal languageTag As String) As String
        'An implementation can reproduce this format without accessing the registry as follows. 
        'All subtags, including extension and private use subtags, 
        'use lowercase letters with two exceptions:  
        'two-letter and four-letter subtags that neither  
        'appear at the start of the tag nor occur after singletons. 
        'Such two-letter subtags are all uppercase (as in the tags "en-CA-x-ca" or "sgn-BE-FR")  
        'and four-letter subtags are titlecase (as in the tag "az-Latn-x-latn").  
        Dim builder As New StringBuilder(languageTag) 
        Dim startIndex As Integer       '段开始的位置 
        Dim afterSingleton As Boolean   '标记是否之前出现了单个字符的段(可能表示私有用途) 
        With builder 
            For I = 0 To .Length - 1 
                If .Chars(I) = "_"c Then
                    '将 Unicode 允许的下划线分隔转化为横杠 
                    .Chars(I) = "-"c 
                End If
                If .Chars(I) = "-"c Then
                    '分隔符 
                    '小结 
                    Dim segmentLength = I - startIndex      '段长 
                    If segmentLength = 1 Then afterSingleton = True
                    If Not afterSingleton AndAlso startIndex > 0 Then
                        '不是第一段 
                        If segmentLength = 2 Then
                            '大写(区域) 
                            .Chars(I - 1) = Char.ToUpperInvariant(.Chars(I - 1)) 
                            .Chars(I - 2) = Char.ToUpperInvariant(.Chars(I - 2)) 
                        ElseIf segmentLength = 4 Then
                            '首字母大写(脚本) 
                            .Chars(startIndex) = Char.ToUpperInvariant(.Chars(startIndex)) 
                        End If
                    End If
                    startIndex = I + 1 
                Else
                    '小写 
                    .Chars(I) = Char.ToLowerInvariant(.Chars(I)) 
                End If
            Next
            Return .ToString 
        End With
    End Function
    
    ''' <summary> 
    ''' 将指定的语言标记进行回退(fallback)。 
    ''' </summary> 
    ''' <param name="languageTag">待回退的、符合 BCP47(RFC5646) 要求(除大小写外)语言标记。</param> 
    ''' <returns>符合 BCP47(RFC4647) 建议的回退后的语言标记。如果指定的语言标记为空、<c>null</c>,或已无法回退,则返回空字符串。</returns> 
    Public Shared Function Fallback(ByVal languageTag As String) As String
        'In the lookup scheme, the language range is progressively truncated 
        'from the end until a matching language tag is located.  Single letter 
        'or digit subtags (including both the letter 'x', which introduces 
        'private-use sequences, and the subtags that introduce extensions) are 
        'removed at the same time as their closest trailing subtag. 
        If languageTag = Nothing Then
            Return ""
        Else
            Dim LastSeparator = languageTag.LastIndexOf("-"c) 
            If LastSeparator = -1 Then
                '未找到 
                Return ""
            Else
                '存在横杠,至少 languageTag = "-" 
                Dim RV = languageTag.Substring(0, LastSeparator) 
                If RV.Length >= 2 AndAlso RV(RV.Length - 2) = "-"c Then
                    '末端存在单个字符,继续回退 
                    Return Fallback(RV) 
                ElseIf RV.Length = 1 Then
                    '单个字符,直接回退 
                    Return ""
                Else
                    Return RV 
                End If
            End If
        End If
    End Function
    
    Private Sub New() 
    
    End Sub
End Class
Content is available under CC BY-SA 3.0 unless otherwise noted.