佳佳的博客
Menu
首页
《.NET Core 实战》 [No.267~283] 序列化
Posted by
佳佳
on 2020-03-28
IT
C#
.NET Core
《.NET Core 实战》
读书笔记
<!-- # 《.NET Core 实战》 [No.267~283] 序列化 --> <!-- dotnet-core-serialization --> **序列化**(*Serialization* 也叫“串行化”),就是**将某个对象实例的状态信息存储到可传输介质中**,例如内存中、文件中以及通过网络发送的数据中。 实例的状态信息包括 *对象的属性* 和 *字段成员的值*(**不包括方法和事件**)。 **从可传输介质中读取数据,重新为对象的属性或字段成员赋值**的过程称为**反序列化**(*Deserialization*)。 现在用的最多的是 *JSON* 格式。 毕竟这几年前端框架比较流行,而且 *JavaScript* 原生就支持 *JSON* 格式,很容易的就可以转换为对象处理。 第二种用的较多的是 *XML* 格式。功能上来说 XML 格式更强一些,相对的结构也就比较复杂,占用的空间也更多一些。 *.NET* 的 *WebService* 或 *WebApi* 这两种格式都是支持的。 最后一种不大常用的是 **二进制序列化**,如字面意思,就是**将实例的状态信息以二进制的方式保存**。 优点是数据体积小,缺点是不便于在不同的网络平台之间传输。 这个缺点比较明显,所以用的地方比较少。 ## 二进制序列化 要让自定义类型支持二进制序列化,需要在类型上应用 `SerializableAttribute` 。 ```csharp [Serializable] class Person { public string Name { get; set; } public int Age { get; set; } } ``` 二进制序列化用到的是 `BinaryFormatter` 类,在 *System.Runtime.Serialization.Formatters.Binary* 命名空间下。 ```csharp string fileName = "demo.data"; // 序列化到文件 using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate)) { BinaryFormatter ft = new BinaryFormatter(); Person ps = new Person() { Name = "JiaJia", Age = 18, }; ft.Serialize(fs, ps); } // 反序列化 using (FileStream fs = new FileStream(fileName, FileMode.Open)) { BinaryFormatter ft = new BinaryFormatter(); Person ps = ft.Deserialize(fs) as Person; Console.WriteLine($"Name:{ps.Name}\nAge:{ps.Age}"); } ``` 执行结果: ```csharp Name:JiaJia Age:18 ``` ## XML序列化(*XMLSerializer*) XML序列化(`XMLSerializer`)在 *System.Xml.Serialization* 命名空间下,用法同 `BinaryFormatter` 类似。但是 `XMLSerializer` 只能对 *public* 类型进行序列化,而且只序列化公共类型中的公共字段。另外,使用 `XMLSerializer` 时不需要在类型上应用 `SerializableAttribute` 。 ```csharp public class Person { public string Name { get; set; } public int Age { get; set; } } ``` ```csharp using (MemoryStream ms = new MemoryStream()) { Person ps = new Person() { Name = "JiaJia", Age = 18, }; // 序列化 XmlSerializer sz = new XmlSerializer(ps.GetType()); sz.Serialize(ms, ps); // 读取XML文档 ms.Position = 0L; using (StreamReader reader = new StreamReader(ms, Encoding.UTF8, false, (int)ms.Length, true)) { Console.WriteLine(reader.ReadToEnd()); } // 反序列化 ms.Position = 0L; XmlSerializer dsz = new XmlSerializer(typeof(Person)); Person dps = dsz.Deserialize(ms) as Person; Console.WriteLine($"Name:{dps.Name}\nAge:{dps.Age}"); } ``` 执行结果: ```xml <?xml version="1.0"?> <Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Name>JiaJia</Name> <Age>18</Age> </Person> Name:JiaJia Age:18 ``` 使用 `XMLSerializer` 若需要对元素名等进行自定义,需要使用如下几个特性: - `XmlRootAttribute`:应用于类型,自定义根元素的名称(默认为类名); - `XmlElementAttribute`:应用于类型成员,自定义生成的元素名称(默认为类型成员名); - `XmlArrayAttribute`:应用于集合类型成员,自定义集合项的元素名称(默认为类型成员名); - `XmlArrayItemAttribute`:应用于集合类型成员,自定义集合项的子元素的名称(默认为集合成员的类型名); - `XmlAttributeAttribute`:应用于类型成员,将类型成员序列化为元素的属性; 上述特性都在 *System.Xml.Serialization* 命名空间下,而且都支持使用 *Namespace* 参数指定元素的命名空间。 下面示例展示了使用上述几种特性后的序列化结果。 ```csharp [XmlRoot("Psn", Namespace = "liujiajia.me")] public class Person { [XmlElement("Nm", Namespace = "liujiajia.me/Name")] public string Name { get; set; } [XmlAttribute("Ag", Namespace = "liujiajia.me/Age")] public int Age { get; set; } [XmlArray("Hsts", Namespace = "liujiajia.me/Histories")] [XmlArrayItem("Hst", Namespace = "liujiajia.me/History")] public string[] Histories { get; set; } } ``` ```xml <?xml version="1.0"?> <Psn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" d1p1:Ag="18" xmlns:d1p1="liujiajia.me/Age" xmlns="liujiajia.me"> <Nm xmlns="liujiajia.me/Name">JiaJia</Nm> <Hsts xmlns="liujiajia.me/Histories"> <Hst xmlns="liujiajia.me/History">A</Hst> <Hst xmlns="liujiajia.me/History">B</Hst> </Hsts> </Psn> ``` ## 数据协定(*DataContract*) **数据协定** 是 **一种约定**,它要求 **参与约定的类型 以及 其成员结构 必须匹配**,但 **类型 以及 类型的成员名称 不一定相同**。 简单点说就是 **序列化后的结构要相同**。 数据协定最大的作用是**在网络传输中保证数据模型的统一**。 使用数据协定序列化为 XML 类型时使用 `DataContractSerializer` 类,序列化为 JSON 格式时使用 `DataContractJsonSerializer` 类。 两者的使用方法时相同的,调用 *WriteObject* 执行序列化,调用 *ReadObject* 执行反序列化。 使用数据协定时,序列化的类型需要满足下面要求中的一种: - 在类型上应用 `DataContractAttribute`,在需要序列化的成员上应用 `DataMemberAttribute` ; - 类型是 *public* 的,此时所有 *public* 的成员都将被序列化; 上述两个要求都不满足时,会报如下错误: ```csharp System.Runtime.Serialization.InvalidDataContractException HResult=0x80131500 Message=Type 'DataContractDemo.Person' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. Alternatively, you can ensure that the type is public and has a parameterless constructor - all public members of the type will then be serialized, and no attributes will be required. Source=System.Private.DataContractSerialization StackTrace: at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type) at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Type type) at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type) at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type) at System.Runtime.Serialization.DataContract.GetDataContract(RuntimeTypeHandle typeHandle, Type type, SerializationMode mode) at System.Runtime.Serialization.DataContractSerializer.get_RootContract() at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph) at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(Stream stream, Object graph) at DataContractDemo.Program.Main(String[] args) in C:\Users\Administrator\source\repos\SerializationDemo\DataContractDemo\Program.cs:line 22 ``` 当使用 `DataContractAttribute` 和 `DataMemberAttribute` 时,可以通过设置其 *Name* 属性的值来自定义序列化后的元素名。`DataContractAttribute` 还以设置 *Namespace* 参数来指定根元素的命名空间(由于 *JSON* 没有根元素,此时 *Name* 和 *Namespace* 参数无效)。`DataMemberAttribute` 可以通过 *Order* 参数改变序列化时成员的顺序,默认是根据成员名称升序排列的。 另外还可以在成员上应用 `IgnoreDataMemberAttribute` 阻止成员被序列化。 ```csharp [DataContract(Namespace = "liujiajia.me")] public class Person { [DataMember(Order = 1)] public string Name { get; set; } [DataMember(Order = 2)] public int Age { get; set; } } ``` ### 序列化为 *XML* 格式 ```csharp using (MemoryStream ms = new MemoryStream()) { Person ps = new Person() { Name = "JiaJia", Age = 18, }; // 序列化到内存流 DataContractSerializer szr = new DataContractSerializer(ps.GetType()); szr.WriteObject(ms, ps); // 读取序列化结果 ms.Position = 0L; using (StreamReader reader = new StreamReader(ms, Encoding.UTF8, false, (int)ms.Length, true)) { Console.WriteLine(reader.ReadToEnd()); } // 反序列化 ms.Position = 0L; DataContractSerializer dsz = new DataContractSerializer(typeof(Person)); Person dps = dsz.ReadObject(ms) as Person; Console.WriteLine($"Name:{dps.Name}\nAge:{dps.Age}"); } ``` 和使用 `XMLSerializer` 相比 `DataContractSerializer` 会删除 *XML* 文档中的空白字符,以压缩文档体积,便于网络传输。 ```xml <Person xmlns="liujiajia.me" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Name>JiaJia</Name><Age>18</Age></Person> Name:JiaJia Age:18 ``` `DataContractSerializer` 支持保留实例引用,开启后,序列化时会为每个实例分配一个 id,以保证每个实例在序列化时只生成一次。如果某个实例被多个成员重复引用,那么之后实例第一次出现时才会填充数据,以缩减文档的长度。 ```csharp using (MemoryStream ms = new MemoryStream()) { Person ps = new Person() { Name = "JiaJia", Age = 18, }; List<Person> pss = new List<Person>() { ps, ps, ps, }; // 序列化到内存流 DataContractSerializerSettings settings = new DataContractSerializerSettings(); // 开启保留实例引用 settings.PreserveObjectReferences = true; DataContractSerializer szr = new DataContractSerializer(pss.GetType(), settings); szr.WriteObject(ms, pss); // 读取序列化结果 ms.Position = 0L; using (StreamReader reader = new StreamReader(ms)) { Console.WriteLine(reader.ReadToEnd()); } } ``` 序列化结果如下: ```xml <ArrayOfPerson z:Id="1" z:Size="3" xmlns="liujiajia.me" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"><Person z:Id="2"><Name z:Id="3">JiaJia</Name><Age>18</Age></Person><Person z:Ref="2" i:nil="true"/><Person z:Ref="2" i:nil="true"/></ArrayOfPerson> ``` ### 序列化为 *JSON* 格式 序列化为 *JSON* 格式使用 `DataContractJsonSerializer` 类,用法同 `DataContractSerializer` ```csharp using (MemoryStream ms = new MemoryStream()) { Person ps = new Person() { Name = "JiaJia", Age = 18, }; // 序列化到内存流 DataContractJsonSerializer szr = new DataContractJsonSerializer(ps.GetType()); szr.WriteObject(ms, ps); // 读取序列化结果 ms.Position = 0L; using (StreamReader reader = new StreamReader(ms, Encoding.UTF8, false, (int)ms.Length, true)) { Console.WriteLine(reader.ReadToEnd()); } // 反序列化 ms.Position = 0L; DataContractJsonSerializer dsz = new DataContractJsonSerializer(typeof(Person)); Person dps = dsz.ReadObject(ms) as Person; Console.WriteLine($"Name:{dps.Name}\nAge:{dps.Age}"); } ``` 执行结果如下: ```json {"Name":"JiaJia","Age":18} Name:JiaJia Age:18 ``` --- > 购买本书 => [《.NET Core实战:手把手教你掌握380个精彩案例》][10] -- *周家安* 著 --- [10]:https://union-click.jd.com/jdc?e=&p=AyIGZRhaEwAQBFUZXBIyEgRSEl0QCxc3EUQDS10iXhBeGlcJDBkNXg9JHU4YDk5ER1xOGRNLGEEcVV8BXURFUFdfC0RVU1JRUy1OVxUBFQ5THlIQMm1AEkRdb11GZyNTK0BBZwYIbylWcHILWStaJQITBlYbXB0LFQJlK1sSMkBpja3tzaejG4Gx1MCKhTdUK1sRCxQBVxtTEQIQBlwrXBULIloNXwZBXUReEStrJQEiN2UbaxYyUGlUG1kUBhcGUBILQgUXDlMeUkBVRlUBS10XBkIABhkJRzIQBlQfUg%3D%3D (《.NET Core实战:手把手教你掌握380个精彩案例》)
版权声明:原创文章,未经允许不得转载。
https://www.liujiajia.me/2020/3/28/dotnet-core-serialization
“Buy me a nongfu spring”
« 《.NET Core 实战》 [No.301~303] Socket 通信
《.NET Core 实战》 [No.265~266] 命名管道 »
昵称
*
电子邮箱
*
回复内容
*
(回复审核后才会显示)
提交
目录
AUTHOR
刘佳佳
江苏 - 苏州
软件工程师