C#笔记——7.序列化与反序列化

序列化与反序列化简介

序列化指将对象转换为字节流的过程,与之相反的便是反序列化,即将字节流转换为对象的过程。

.NET中进行对象序列化的几种方式:

二进制序列化:对象序列化之后是二进制形式的,通过System.Runtime.Serialization.Formatters.Binary命名空间下的BinaryFormatter类来实现序列化操作。

SOAP序列化:对象序列化之后的结果符合SOAP协议,即可以使用SOAP协议传输,通过System.Runtime.Serialization.Formatters.Soap命名空间下的SoapFormatter类来实现序列化操作。

XML序列化:将对象序列化为XML形式,可以通过位于System.Xml.Serialization命名空间下XmlSerializer类来实现,要读取XML中的数据可以直接使用XmlTextReader、XmlDocument、XPath, 也可以使用LINQ TO XML或者反序列化的方法从XML中读取数据。

Json序列化:通过序列化将.net对象转换为JSON字符串,在.NET中操作JSON,可以使用LitJson或者Json.NET,也可以使用.NET内建的System.Runtime.Serialization.Json,相比较而言Json.NET功能更加强大一些,JSON.NET的LINQ to JSON也可以根据需求的格式来定制json数据。

Protobuf序列化:在.net中操作Protobuf,可使用protobuf-net或者Google.Protobuf,protobuf是一种效率和兼容性都很优秀的二进制数据传输格式,速度快、体积小是其主要优点,主要是因为protobuf将数据序列化为二进制之后,占用的空间相当小,基本仅保留了数据部分,xml和json则附带消息结构在数据中。

Binary

BinaryFormatter的使用

案例代码:

public static class BinaryTools
{
    public static void Serialize(object obj, string filePath)
    {
        if (obj == null) throw new ArgumentNullException(" obj  is null.");
        if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("FilePath is null or empty .");

        //构建存储序列化目标对象之后生成的字节块的容器即流对象
        using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
        {
            try
            {
                //构建binaryFormatter格式化器
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                //调用格式化器的Serialize()方法
                binaryFormatter.Serialize(fileStream, obj);
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Filed To Serialization : " + e.Message);
                throw;
            }
        }
    }

    public static T Deserialize<T>(string path) where T : class
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("Path is null or empty .");

        //构建一个用于读取相应文件的流对象
        using (Stream tempStream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
        {
            try
            {
                //构建binaryFormatter格式化器
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                //调用格式化器的DeSerialize()方法,并返回反序列化后得到的对象
                T temp = binaryFormatter.Deserialize(tempStream) as T;
                return temp;
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Filed To Serialization : " + e.Message);
                throw;
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<Developer> UserList = new List<Developer> {
            new Developer("Sylvan", "1320486310", false, 25,"C/C++") ,
            new Developer("Bob", "6516135156", false, 26,"C++/Java/C#"),
            new Developer("Dane", "8641864613", true, 24,"C#/JS/Lua"),
            new Developer("Cathy", "848464138", false, 26,"C++/OC/Java"),
            new Developer("Zevin", "135416516", true, 25,"C++/C#/OC/Java")
        };

        string filePath = "UserData.dat";

        BinaryTools.Serialize(UserList, filePath);

        UserList.Clear();

        UserList = BinaryTools.Deserialize<List<Developer>>(filePath);

        foreach (var u in UserList)
        {
            Console.WriteLine(u.ToString());
        }

        Console.ReadKey();
    }
}

测试代码:

[Serializable]//在定义需要进行序列化操作的类型时,其父类也必须应用[Serializable]特性
public class Person
{
    public String Name { get; set; }
    public String ID { get; set; }
    public bool Gender { get; set; }
    public int Age { get; set; }

    public Person(string name, string id, bool gender, int age) {
        Name = name;
        ID = id;
        Gender = gender;
        Age = age;
    }

    public override string ToString()
    {
        return "Name : " + Name + ", ID : " + ID + " , Gender :" + Gender + " , Age : " + Age;
    }
}

[Serializable]//在定义需要进行序列化操作的类型时,必须应用[Serializable]特性
public class Developer : Person
{
    public String Languages { get; set; }
    public Developer(string name ,string id , bool gender,int age ,string language):base(name,id,gender,age) {
        Languages = language;
    }
    public override string ToString()
    {
        return base.ToString() + ", Work with the languages : " + Languages+" .";
    }
}

Soap

SOAP-简单对象访问协议(SOAP)是一种轻量的、简单的、基于 XML 的协议,它被设计成在 WEB 上交换结构化的和固化的信息。 使用前需要引用System.Runtime.Serialization.Formatters.Soap.dll程序集并添加System.Runtime.Serialization.Formatters.Soap命名空间。其实Soap和二进制序列化步骤是一样的,只是换了一个序列化器,把BinaryFormatter实例对象换成SoapFormatter对象实例 ,格式化器把对象序列化成SOAP消息或者解析SOAP消息并重构被序列化的对象。

序列化后保存的SoapFileExample用VS打开之后的样子:

<SOAP-ENV:Body>

<a1:Developer id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/SerializationTest/SerializationTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">

<_x003C_Languages_x003E_k__BackingField id="ref-3">C/C++</_x003C_Languages_x003E_k__BackingField>

<Person_x002B__x003C_Name_x003E_k__BackingField id="ref-4">Sylvan</Person_x002B__x003C_Name_x003E_k__BackingField>

<Person_x002B__x003C_ID_x003E_k__BackingField id="ref-5">1320486310</Person_x002B__x003C_ID_x003E_k__BackingField>

<Person_x002B__x003C_Gender_x003E_k__BackingField>false</Person_x002B__x003C_Gender_x003E_k__BackingField>

<Person_x002B__x003C_Age_x003E_k__BackingField>25</Person_x002B__x003C_Age_x003E_k__BackingField>

</a1:Developer>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

测试代码:

public static class SoapTools
{
    public static void Serialize(object obj,string filePath) {
        if (obj == null) throw new ArgumentNullException(" obk  is null.");
        if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("FilePath is null or empty .");

        //构建存储序列化目标对象之后生成的Soap文件的流对象即容器
        using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
        {
            try
            {
                //构建soapFormatter格式化器
                SoapFormatter soapFormatter = new SoapFormatter();
                //调用soapFormatter的Serialize()方法
                soapFormatter.Serialize(fileStream, obj);
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Filed To Serialization : " + e.Message);
                throw;
            }
        }
    }

    public static T Deserialize<T>(string path) where T : class
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("Path is null or empty .");

        //构建读取相应soap文件的流对象
        using (Stream tempStream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
        {
            try
            {
                //构建soapFormatter格式化器
                SoapFormatter soapFormatter = new SoapFormatter();
                //调用DeSerialize()方法
                T temp = soapFormatter.Deserialize(tempStream) as T;
                //返回被序列化的对象
                return temp;
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Filed To Serialization : " + e.Message);
                throw;
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Developer User1 = new Developer("Sylvan", "1320486310", false, 25, "C/C++");

        string fileName = "SoapFileExample";

        SoapTools.Seralize(User1, fileName);
        //更改User1里面字段的值
        User1.Languages = "C/C++/OC/C#/Java";
        //为User1重新赋值为反序列化后的对象
        User1 = SoapTools.DeSerialize<Developer>(fileName);

        Console.WriteLine(User1.ToString());
        Console.ReadKey();
    }
}

XML

“XML序列化是将一个对象或参数的公开字段和属性以及方法的返回值转换(序列化)成遵循XSD文档标准的XML流。因为XML是一个开放的标准,即XML能被任何需要的程序处理,而不管在什么平台下,因此XML序列化被用到带有公开的属性和字段的强类型类中,它的这些发生和字段被转换成序列化的格式(在这里是XML)存储或传输。”

.Net对XML提供了很多API,System.Xml 命名空间为处理 XML 提供基于标准的支持;System.Xml.Serialization 命名空间包含用于将对象序列化为 XML 格式的文档或流的类;
System.Xml.XPath 命名空间包含定义为定位和编辑 XML 信息项作为 XQuery 1.0 和 XPath 2.0 数据模型的实例的游标模型的类。

这些API根据不同的使用场景实现了不同层次的封装, 我们可以直接使用XmlTextReader、XmlDocument或者XPath来取数XML中的数据, 也可以使用LINQ TO XML或者反序列化的方法从XML中读取数据。

XmlSerilizer的使用

案例代码:

public static class XmlTools
{

    public static void Serialize<T>(T obj, string filePath = "") where T : new()
    {
        if (obj == null) throw new ArgumentNullException("obj is null");
        //构建一个TextWriter类型的实例,其StreamWriter类的构造函数参数指定一个特定的文档的路径(使用默认的编码和缓冲区大小)
        using (TextWriter textWriter = new StreamWriter(filePath))
        {
            //构建Xml序列化器,XmlSerializer 的构造函数必须知道所需要进行序列化对象的类型 — 表示的 XML 根元素的类的类型。
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            //调用XmlSerializer.Serialize()方法来序列化目标对象
            xmlSerializer.Serialize(textWriter, obj);
        }
    }

    //使用XmlWriter写入Xml文档
    public static void SerializeForm<T>(T obj, String path)
    {
        if (obj == null) throw new ArgumentNullException("obj is null");
        if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path is null or empty .");

        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.NewLineChars = "\r\n";
        settings.Encoding = Encoding.UTF8;
        settings.IndentChars = "    ";

        //使用XmlWriter.Create()方法构建XmlWriter类型的对象,指定特定文档和写入设置
        using (XmlWriter xmlWriter = XmlWriter.Create(path, settings))
        {
            //构建Xml序列化器,XmlSerializer 的构造函数必须知道所需要进行序列化对象的类型 — 表示的 XML 根元素的类的类型。
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            xmlSerializer.Serialize(xmlWriter, obj);
        }
    }

    public static T DeSerialize<T>(string filePath) where T : class
    {
        if (string.IsNullOrEmpty(filePath)) throw new ArgumentException("filePath is null or Empty .");

        //构建一个TextReader类型的实例,构造函数参数指定一个特定的文档的路径(使用默认的编码和缓冲区大小)
        using (TextReader textReader = new StreamReader(filePath))
        {
            //构建Xml序列化器,XmlSerializer 的构造函数必须知道所需要进行序列化对象的类型 — 表示的 XML 根元素的类的类型。
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            //调用XmlSerializer.Deserialize()方法,对指定的xml文档进行反序列化
            T temp = xmlSerializer.Deserialize(textReader) as T;
            return temp;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        List<Developer> UserList = new List<Developer> {
            new Developer("Sylvan", "1320486310", false, 25,"C/C++") ,
            new Developer("Bob", "6516135156", false, 26,"C++/Java/C#"),
            new Developer("Dane", "8641864613", true, 24,"C#/JS/Lua")
        };

        string filePath = "UserList.xml";
        XmlTools.Serialize<List<Developer>>(UserList, filePath);

        UserList.Clear();

        UserList = XmlTools.DeSerialize<List<Developer>>(filePath);

        foreach (var u in UserList)
        {
            Console.WriteLine(u.ToString());
        }

        Console.ReadKey();
    }
}

序列化过程

上面代码中,序列化目标对象时,首先构建Xml序列化器,XmlSerializer 的构造函数必须知道所需要进行序列化对象的类型—表示的 XML 根元素的类的类型;

接着,构建一个TextWriter类型实例,构造函数的参数指定一个特定的文档的路径,此时写入Xml文档时使用默认的编码和缓冲区大小(也可以Create一个XmlWriter类型的对象,XmlWriter可以把Xml写入流、文档、StringBuilder、TextWriter或者另外一个XmlWriter对象);

之后,调用XmlSerializer.Serialize()方法对目标对象obj进行序列化操作,向指定的文件写入Xml文档();

最后,关闭TextWriter。

输出XML文档实例:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfDeveloper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Developer>
    <Name>Sylvan</Name>
    <ID>1320486310</ID>
    <Gender>false</Gender>
    <Age>25</Age>
    <Languages>C/C++</Languages>
    </Developer>
    <Developer>
    <Name>Bob</Name>
    <ID>6516135156</ID>
    <Gender>false</Gender>
    <Age>26</Age>
    <Languages>C++/Java/C#</Languages>
    </Developer>
    <Developer>
    <Name>Dane</Name>
    <ID>8641864613</ID>
    <Gender>true</Gender>
    <Age>24</Age>
    <Languages>C#/JS/Lua</Languages>
    </Developer>
</ArrayOfDeveloper>

XmlReader的使用

SAX(Simple API for XML)简介:SAX解析方式会逐行地去扫描XML文档,当遇到标签时会触发解析处理器,采用事件处理的方式解析XML,不是官方标准,但它是 XML 社区事实上的标准,几乎所有的 XML 解析器都支持它。优点是:在读取文档的同时即可对XML进行处理,不必等到文档加载结束,相对快捷。不需要加载进内存,因此不存在占用内存的问题,可以解析超大XML。缺点是:只能用来读取XML中数据,无法进行增删改。

使用过SAX的话,就对.Net为SAX提供的替代品XmlReader类和XmlWriter类很熟悉了。XmlReader和 XmlWriter都是抽象类,他们的派生类如下:

  • XmlNodeReader:

  • XmlTextReader: 处理一个流对象或者一个TextReader、TextWriter对象

  • XmlValidatingReader

  • XmlTextWriter

  • XmlQueryWriter

XmlReader类

public static void XmlRead(string path) {
    XmlReader reader = XmlReader.Create(path);

    while (reader.Read()) {
        if (reader.NodeType == XmlNodeType.Element &&  reader.Name == "Name") {
            Console.WriteLine(reader.ReadElementContentAsString());
        }
    }
}

上方代码使用的XmlReader是一个抽象类,要直接使用XmlReader类,就必须使用Create静态方法,该方法返回一个XmlReader对象,方法参数指定一个特定的文档的路径(也可以传递流对象或者TextReader对象)

XmlReader类的相关方法

  • Read()方法可以进入下一节点;
  • HasValue()验证该节点是否有一个值;
  • HasAttribute()验证该节点是否有特性;
  • ReadStartElement()方法验证当前节点是否为起始元素(如果不是则引发一个 XmlException)
  • ReadElementString()读取当前节点存储的属性值

DOM的使用

.Net中的DOM (Document Object Model)

  • 使用DOM的好处在于它允许编辑和更新XML文档,可以随机访问文档中的数据,可以使用XPath查询;
  • 缺点在于它需要一次性的加载整个文档到内存中,对于大型的文档,这会造成资源问题。

DOM通过XmlNode类来实现,XmlNode类是一个抽象类,表示Xml文档的一个节点;还有一个XmlNodeList类,表示节点的一个有序列表,该列表支持迭代器访问。

XmlDocument类
XmlDocument类以及其派生类XmlDataDocument是用于在.Net中表示DOM的类。与XmlReader、XmlWriter不同,XmlDocument类具有读写功能,并且可以随机访问DOM树。

案例代码:

    public static void DomRead(string filePath) {
        XmlDocument xmlDocument = new XmlDocument();

        xmlDocument.Load(filePath);

        XmlNodeList nodelist = xmlDocument.GetElementsByTagName("Name");

        foreach (XmlNode xmlNode in nodelist) {
            Console.WriteLine(xmlNode.InnerXml);
        }
    }

xmlNode.InnerXml将会输出该节点存储的元素属性值,输出结果:

Sylvan
Dane
Zevin

对比XmlReader,当我们的工作非常简单仅需浏览一次文档时,可以使用XmlReader类;当我们需要重复的查看某一节点时,则最好使用XmlDocument类。

Json

JSON的全称是”JavaScript Object Notation”,意思是JavaScript对象表示法,是一种基于文本,独立于语言的轻量级数据交换格式。

Json格式:

  • 一个对象以花括号“{”开始,并以花括号"}"结束,在每一个“键”的后面,有一个冒号,并且使用逗号来分隔多个键值对
  • 一个数组以中括号"["开始,并以中括号"]"结束,并且所有的值或者对象使用逗号分隔
//Json File:UserList.json
{"UsersList":[
    {"Languages":"C/C++","Name":"Sylvan","ID":"1320486310","Gender":false,"Age":25},
    {"Languages":"C#/JS/Lua","Name":"Dane","ID":"8641864613","Gender":true,"Age":24},
    {"Software":"PS/Maya/Max","Name":"Zevin","ID":"8641864613","Gender":true,"Age":24}
]}

LitJson的使用

引入Litjson的两种方式:

方式一:去LitJson下载litjson源文件,Build后为自己项目添加LitJson引用

方式二:在VS中在NuGet工具中使用命令Install-Package LitJson来下载安装

JsonMapper类

可以对JsonMapper类进行简单的封装:

public static class LitJsonTools
{
    public static string Serialize(object obj , string filePath = "")
    {
        if (obj == null) throw new ArgumentNullException(" obj  is null.");
        FileInfo fileInfo = new FileInfo(filePath);
        string str = JsonMapper.ToJson(obj);
        if (!string.IsNullOrEmpty(filePath))
        {
            using (StreamWriter streamWriter = fileInfo.CreateText())
            {
                streamWriter.WriteLine(str);
            }
        }

        return str;
    }

    public static T DeSerialize<T>(string filePath)
    {
        T temp;
        using (StreamReader streamReader = File.OpenText(filePath))
        {
            string str = streamReader.ReadToEnd();
            temp = JsonMapper.ToObject<T>(str);
        }
        return temp;
    }
}

测试代码:

class Program
{
    private const string filePath = "UserList.json";

    static void Main(string[] args)
    {
        Developer dl1 = new Developer("Sylvan", "1320486310", false, 25, "C/C++");
        Developer dl2 = new Developer("Dane", "8641864613", true, 24, "C#/JS/Lua");
        Designer dg1 = new Designer("Zevin", "8641864613", true, 24, "PS/Maya/Max");

        UserData userDatas = new UserData(new List<Person> { dl1, dl2, dg1 });

        //序列化对象到Json文件
        string output = LitJsonTools.Serialize(userDatas,filePath);
        Console.WriteLine("Output Json String : \n" + output);

        //从相应的Json文件路径中反序列化相应对象
        UserData u = LitJsonTools.DeSerialize<UserData>(filePath);
        foreach (Person p in u)
        {
            Console.WriteLine(p.ToString());
        }

        Console.ReadKey();
    }
}

JsonData接收无具体实现的类

class Program
{
    static void Main(string[] args)
    {
        string json_dl1 = @"{
        ""Developer"" : {
            ""Name"" : ""Sylvan"",
            ""ID""  : ""1320486310"",
            ""Gender""  : false,
            ""Age""  : 25,
            ""Language""  : [
                ""C"",
                ""C++"",
                ""C#""
                ]
            }
        }";

        JsonData jd = JsonMapper.ToObject(json_dl1);
        Console.WriteLine("{0} work with {1} . " , jd["Developer"]["Name"],jd["Developer"]["Language"][2]);
        Console.ReadKey();
    }
}

LitJson中的JsonWriter以及JsonReader用法也非常简单,用法可参考官方示例代码

Json.Net的使用

引入引入Litjson的两种方式:的两种方式:

方式一:去Json.Net下载Json.NET源文件,Build后为自己项目添加LitJson引用

方式二:在VS中在NuGet工具中使用命令Install-Package Newtonsoft.Json来下载安装

JsonConvert类

在一些简单的情况,假如想要将一个.Net对象转换成Json字符串,或者反过来,我们可以直接使用静态类JSonConvert的SerializeObjcet()以及DeserializeObject()方法。

class Program
{
    private const string filePath = "UserList.xml";

    static void Main(string[] args)
    {
        Developer dl1 = new Developer("Sylvan", "1320486310", false, 25, "C/C++");
        Developer dl2 = new Developer("Dane", "8641864613", true, 24, "C#/JS/Lua");
        Designer dg1 = new Designer("Zevin", "8641864613", true, 24, "PS/Maya/Max");

        UserData userDatas = new UserData (new List<Person> { dl1,dl2,dg1});

        string output = JsonConvert.SerializeObject(userDatas);

        userDatas = null;

        userDatas = JsonConvert.DeserializeObject<UserData>(output);

        foreach (var  u in userDatas.UsersList) {
            Console.WriteLine(u.ToString());
        }

        Console.ReadKey();
    }
}

JsonSerializer类

在一些复杂点的情况下,我们可以使用JsonSerializer来更好地控制对象的序列化过程。JsonSerializer可以通过JsonTextWriter、JsonTextReader将Json文本直接写入流或者从流中读取出来。

案例代码:

public static class JsonTools {
    public static void Serialize<T>( T obj , string filePath) {
        JsonSerializer serializer = new JsonSerializer();
        serializer.Converters.Add(new JavaScriptDateTimeConverter());
        serializer.NullValueHandling = NullValueHandling.Ignore;
        using (TextWriter writer = new StreamWriter (filePath)) {
            using (JsonWriter jsonWriter = new JsonTextWriter(writer)) {
                serializer.Serialize(jsonWriter,obj);
            }
        }
    }
}

class Program
{
    private const string filePath = "UserList.json";

    static void Main(string[] args)
    {
        Developer dl1 = new Developer("Sylvan", "1320486310", false, 25, "C/C++");
        Developer dl2 = new Developer("Dane", "8641864613", true, 24, "C#/JS/Lua");
        Designer dg1 = new Designer("Zevin", "8641864613", true, 24, "PS、Maya、Max");

        UserData userDatas = new UserData (new List<Person> { dl1,dl2,dg1});

        JsonTools.Serialize(userDatas,filePath);

        Console.ReadKey();
    }
}

Serialization Attributes:

可以对比Xml来看一下Json.Net中提供的属性类:

抽象属性类:JsonContainerAttribute ,其有三个派生类,分别为:

  • JsonObjectAttribute
  • JsonArrayAttribute
  • JsonDictionaryAttribute

剩余的属性类有:

  • JsonPropertyAttribute
  • JsonConverterAttribute
  • JsonExtensionDataAttribute
  • JsonConstructorAttribute

属性类具体使用方法

LINQ To Json

LINQ to Json是一个通过Json object 对象来工作的API,其通过LINQ设计出来,旨在可以快速的查询并且创建Json object,主要位于Newtonsoft.Json.Linq命名空间。

解析Jsoon:

class Program
{
    private const string filePath = "UserList.json";

    static void Main(string[] args)
    {
        Developer dl1 = new Developer("Sylvan", "1320486310", false, 25, "C/C++");
        Developer dl2 = new Developer("Dane", "8641864613", true, 24, "C#/JS/Lua");
        Designer dg1 = new Designer("Zevin", "8641864613", true, 24, "PS、Maya、Max");

        UserData userDatas = new UserData(new List<Person> { dl1, dl2, dg1 });

        string outputStr = JsonConvert.SerializeObject(userDatas);
        Console.WriteLine("输出的Json字符串:" + outputStr);

        FileInfo fileInfo = new FileInfo(filePath);

        using (StreamWriter streamWriter = fileInfo.CreateText())
        {
            streamWriter.WriteLine(outputStr);
        }

        using (StreamReader streamReader = File.OpenText(filePath))
        {
            string jsonStr = streamReader.ReadToEnd();

            //使用Newtonsoft.Json.Linq.JObject来快速的查询并且创建Json object
            JObject obj = JObject.Parse(jsonStr);
            Console.WriteLine((string)obj["UsersList"][0]["Name"]);
        }
        Console.ReadKey();
    }
}

Protobuf

Protocol Buffer(简称protobuf)是一种效率和兼容性都很优秀的二进制数据传输格式,速度快、体积小是其主要优点,主要是因为protobuf将数据序列化为二进制之后,占用的空间相当小,基本仅保留了数据部分,xml和json则附带消息结构在数据中。

在.Net中使用protobuf可以使用谷歌官方的Google.Protobuf或者.Net社区版本protobuf-net,这里选用.Net社区版protobuf-net,引入方式可以选择在VS上使用Nuget的命令进行protobuf-net的简单安装:Install-Package protobuf-net。

和Xml、Json类似,序列化已有对象时需要对目标类应用相应的属性:

[ProtoContract]
[ProtoInclude(5,typeof(Developer))]
[ProtoInclude(6,typeof(Designer))]
public class Person {
    [ProtoMember(1)]
    public String Name { get; set; }
    [ProtoMember(2)]
    public String ID { get; set; }
    [ProtoMember(3)]
    public bool Gender { get; set; }
    [ProtoMember(4)]
    public int Age { get; set; }

    public Person(string name, string id, bool gender, int age) {
        Name = name;
        ID = id;
        Gender = gender;
        Age = age;
    }
    public Person() { }

    public override string ToString()
    {
        return "Name : " + Name + ", ID : " + ID + " , Gender :" + Gender + " , Age :  " + Age;
    }
}

[ProtoContract]
public class Developer : Person {
    [ProtoMember(1)]
    public String Languages { get; set; }
    public Developer(string name, string id, bool gender, int age, string language) : base(name, id, gender, age) {
        Languages = language;
    }
    public Developer() : base() { }
    public override string ToString()
    {
        return base.ToString() + " , Work with the languages : " + Languages + " .";
    }
}

[ProtoContract]
public class Designer : Person {
    [ProtoMember(1)]
    public string Software { get; set; }
    public Designer(string name , string id ,bool gender , int age , string software) : base(name,id,gender,age) {
        Software = software;
    }

    public Designer() : base() { }

    public override string ToString()
    {
        return base.ToString() + ", Work with software : "+ Software +  " ." ;
    }

}

测试代码:

class Program
{
    private const string filePath = "UserList.proto";

    static void Main(string[] args)
    {
        Developer dl1 = new Developer("Sylvan", "1320486310", false, 25, "C/C++");
        Developer dl2 = new Developer("Dane", "8641864613", true, 24, "C#/JS/Lua");
        Designer dg1 = new Designer("Zevin", "8641864613", true, 24, "PS、Maya、Max");

        List<Person> UserList = new List<Person> { dl1,dl2,dg1};

        using (FileStream fileStream = File.Create(filePath)) {
            Serializer.Serialize(fileStream,UserList);
        }

        UserList = null;

        using (FileStream fileStream = File.OpenRead(filePath)) {

            UserList = Serializer.Deserialize<List<Person>>(fileStream);
        }

        foreach (Person u in UserList) {
            Console.WriteLine(u.ToString());
        }

        Console.ReadKey();

    }
}

几种方式的具体使用

二进制格式和SOAP格式可序列化一个类型的所有可序列化字段,不管它是公共字段还是私有字段。而XML格式仅能序列化公共字段或拥有公共属性的私有字段,未通过属性公开的私有字段将被忽略。

SOAP格式序列化通过使用XML命名空间来持久化原始程序集信息。而XML格式序列化不会保存完整的类型名称或程序集信息。这便利XML数据表现形式更有终端开放性。

用protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一(极端情况下,会大于等于直接序列化)

二进制序列化的控制

Serializable特性:

在案例代码中,我们首先对需要进行序列化操作的类型应用了[Serializable]的特性,如果对并没有应用[Serializable]特性的类进行序列化则会抛出“Type *** is not Marked as Serializable .”的异常。

[Serializable]特性可以被应用于类、委托、结构以及枚举(枚举类型和委托类型总是会被序列化的,所以无需显式的继承[Serializable]特性)。

在类的继承中,基类应用的[Serializable]特性并不会被其子类继承,而基类的[NonSerialized]特性则会被子类继承;若基类并没有应用[Serializable]特性,而子类应用了[Serializable],此时的子类依然无法进行序列化操作。

NonSerialized特性:

和类型应用[Serializable]特性使之可序列化相似,我们可以通过在不需要进行序列化的字段上应用[NonSerialized]特性来标识该字段在对象序列化时不被序列化。

序列化流程控制特性:

在进行序列化和反序列化的操作时,我们可以通过在方法上应用System.Runtime.Serialization命名空间下的OnSerializingAttribute\OnAerializedAttribute\OnDeSerializingAttribute\OnDeSerializedAttribute等特性来对序列化、反序列化前后进行一些控制操作。

这些方法的参数必须为StreamingContext类型的,即使该方法获取流的上下文,并且访问修饰符一般为private。

序列化过程:

上面代码中,在进行序列化时,首先构造了一个System.IO.FileStream类型的对象,为序列化过程提供一个容纳序列化生成的字节块的容器;

接着构造一个System.Runtime.Serialization.Formatter.Binary.BinaryFormatter对象(BinaryFormatter格式化器,实现了System.Runtime.Serialization.IFormatters接口,因此,格式化器才可以对对象进行序列化和反序列化操作);

之后,调用格式化器binaryFormatter的Serialize()方法,Serialize()方法签名如下:

public void Serialize(Stream serializationStream , object graph)

Serialize()方法需要两个参数,第一个参数的类型是System.IO.Stream类型或者其派生类的类型,即表示对流对象(存放序列化之后产生字节块的容器)的引用;另外一个参数是System.Object类型的,即该参数可以是任意类型的实例(无论值类型或者引用类型)。

二进制序列化实现细节:

Serialize()方法:

调用该方法时,首先,格式化器会参考目标对象类型的元数据,用来了解需要进行序列化的对象的信息,即Serialize()方法会利用反射机制来查看每个对象的类型中都有哪些实例字段,凡是在字段中引用的对象都会被Serialize()方法序列化,写入流的内容包括类型的全名以及类型定义程序集的全名;

接着,在进行序列化时,格式化器会保证每个对象都仅进行一次序列化操作;

之后,Serialize()方法执行完毕并且返回之后,在我们提供的流对象中便容纳了目标对象序列化之后得到的字节块(对于获得的流对象我们可以根据需要来进行网络传输或者保存到硬盘上);

最后,流对象使用完毕之后,使用Stream.Close()方法关闭当前流并释放与之关联的资源。

反序列化过程:

上面代码中,在进行反序列化时,与序列化过程相似,首先构建了一个Stream类型的流对象,用来读取相应流文件;

接着构建System.Runtime.Serialization.Formatter.Binary.BinaryFormatter类型的对象,即格式化器;

之后,调用格式化器的Deserialize()方法,Deserialize()方法的签名如下:

public object Deserialize(Stream serializationStream)

可以看出Deserialize()方法仅有一个参数,参数的类型是System.IO.Stream类型,并且返回值是一个System.Object类型的。

Deserialize()方法:

在执行Deserialize()方法时,在进行反序列化时,格式化器会首先检查完流中内容,之后会开始获取流中程序集的标识信息,然后再通过调用System.Reflection.Assembly类中的Load()方法将目标程序集加载进当前的AppDomain中,在程序集加载完成之后,格式化器才能在程序集中查找需要进行反序列化的对象的相关类型信息;
获取相应的类型信息之后,便开始构造目标类型的实例。

在对象的实例构造完成之后会根据流中相应字段对对象实例中的字段进行赋值操作,使反序列化之后得到的对象与序列化之前目标对象包含的字段的值保持一致;

Deserialzie()方法会返回一个System.Object类型的对象的引用,得到返回值之后,我们便可以根据需要将获得的对象进行强制的类型转换。

最后,流对象使用完毕之后,使用Stream.Close()方法关闭当前流并释放与之关联的资源。

Xml序列化的控制

XmlElement

修改下测试用的类:

public class User {
    public string Name { get; set; }
    public string Id { get; set; }
    public bool Gender { get; set; }

    public User() { }

    public User(string name,string id , bool gender) {
        Name = name;
        Id = id;
        Gender = gender;
    }
}

对该类的一个实例进行序列化之后得到的XML文档中我们可以看出:

类属性Name、Id、Gender序列化成了XmlElement,即默认情况下(不加任何Attribute),类型中公有的属性或者字段,都会生成XmlElement。

<?xml version="1.0" encoding="utf-8"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<Name>Sylvan</Name>

<Id>5616851681</Id>

<Gender>false</Gender>

</User>

XmlAttribute

修改下测试用的类属性:

public class User {
    [XmlElement]
    public string Name { get; set; }
    [XmlAttribute]
    public string Id { get; set; }
    [XmlAttribute]
    public bool Gender { get; set; }

    public User() { }

    public User(string name,string id , bool gender) {
        Name = name;
        Id = id;
        Gender = gender;
    }
}

将修改后的类的实例序列化生成的XMl文档中可以看出应用了[XmlElement]属性的类属性Name依然是默认的XmlElement;应用了[XmlAttribute]属性的类属性Id、Gender则是XmlAttribute。

<?xml version="1.0" encoding="utf-8"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Id="5616851681" Gender="false">
  <Name>Sylvan</Name>
</User>

InnerText

继续修改下测试用的类:

public class User {
    [XmlText]
    public string Name { get; set; }
    [XmlAttribute]
    public string Id { get; set; }
    [XmlAttribute]
    public bool Gender { get; set; }

    public User() { }

    public User(string name,string id , bool gender) {
        Name = name;
        Id = id;
        Gender = gender;
    }
}

将修改后的类的实例序列化生成的XMl文档中可以看出应用了[XmlAttribute]属性的类属性Id、Gender都生成XmlAttribute,应用了[XmlText]属性的Name属性生成了InnerText(文本节点)。

<?xml version="1.0" encoding="utf-8"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Id="5616851681" Gender="false">Sylvan</User>

重命名

通过序列化得到的XmlElement和XmlAttribute都与类型的数据成员或者类型同名。 当我们可以希望让属性名与XML的节点名称不一样时,可以在应用[XmlType]、[xmlElement]、[XmlAttribute]时,给属性类加上参数,例如[XmlType("UserData")]、[xmlElement("UserName")]、[XmlAttribute("UserId")]

修改后的测试类代码:

public class User {
    [XmlText]
    public string Name { get; set; }
    [XmlAttribute("UserId")]
    public string Id { get; set; }
    [XmlAttribute("UserGender")]
    public bool Gender { get; set; }

    public User() { }

    public User(string name,string id , bool gender) {
        Name = name;
        Id = id;
        Gender = gender;
    }
}

生成XMl文档:

<?xml version="1.0" encoding="utf-8"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" UserId="5616851681" UserGender="false">Sylvan</User>

序列化列表和数组

测试代码:

class Program
{
    private const string filePath = "UserList.xml";

    static void Main(string[] args)
    {
        User u1 = new User("Sylvan","5616851681",false);
        User u2 = new User("Bob","4684646551",false);
        User u3 = new User("Cathy","68416511",true);

        List<User> UserList = new List<User> { u1,u2,u3};

        XmlTools.Serialize(UserList, filePath);
        Console.ReadKey();
    }
}

序列化后生成XMl文档:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <User UserId="5616851681" UserGender="false">Sylvan</User>
  <User UserId="4684646551" UserGender="false">Bob</User>
  <User UserId="68416511" UserGender="true">Cathy</User>
</ArrayOfUser>

可以看出该Xml文档的根节点名字是"ArrayOfUser",我们也可以根据自己需要更改该名称:

新增一个类并继承List<User>,应用[XmlType("Example")]属性即可:

class Program
{
    private const string filePath = "UserList.xml";

    static void Main(string[] args)
    {
        User u1 = new User("Sylvan","5616851681",false);
        User u2 = new User("Bob","4684646551",false);
        User u3 = new User("Cathy","68416511",true);

        //List<User> UserList = new List<User> { u1,u2,u3};

        UserData UserList = new UserData { u1,u2,u3};

        XmlTools.Serialize(UserList, filePath);

        Console.ReadKey();
    }
}

[XmlType(V)]
public class UserData : List<User>
{
    private const string V = "UserList";
}

修改后序列化生成的XML文档,根节点名字改为:UserList

<?xml version="1.0" encoding="utf-8"?>
<UserList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <User UserId="5616851681" UserGender="false">Sylvan</User>
  <User UserId="4684646551" UserGender="false">Bob</User>
  <User UserId="68416511" UserGender="true">Cathy</User>
</UserList>

列表和数组作为数据成员被序列化

新增测试类,包含列表作为类成员:

class Program
{
    private const string filePath = "UserList.xml";

    static void Main(string[] args)
    {
        User u1 = new User("Sylvan","5616851681",false);
        User u2 = new User("Bob","4684646551",false);
        User u3 = new User("Cathy","68416511",true);

        //List<User> UserList = new List<User> { u1,u2,u3};

        ValidUser validUser = new ValidUser(new List<User> { u1,u2,u3},"Android");

        XmlTools.Serialize(validUser, filePath);

        Console.ReadKey();
    }
}

public class ValidUser
{
    [XmlAttribute]
    public string Platform { get; set; }

    public List<User> validUserData = new List<User> ();

    public ValidUser() { }

    public ValidUser(List<User> data, string platform)
    {
        validUserData = data;
        Platform = platform;
    }

}

序列化 生成的Xml文档:

<?xml version="1.0" encoding="utf-8"?>
<ValidUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Platform="Android">

  <validUserData>

    <User UserId="5616851681" UserGender="false">Sylvan</User>

    <User UserId="4684646551" UserGender="false">Bob</User>

    <User UserId="68416511" UserGender="true">Cathy</User>

  </validUserData>

</ValidUser>

与重命名XmlElement、XmlAttribute类似,可以使用[XmlArray("example")]、[XmlArrayItem("example")]进行节点的重命名操作:

public class ValidUser
{
    [XmlAttribute]
    public string Platform { get; set; }

    [XmlArrayItem("RenameUser")]
    [XmlArray("RenameUserData")]
    public List<User> validUserData = new List<User> ();

    public ValidUser() { }

    public ValidUser(List<User> data, string platform)
    {
        validUserData = data;
        Platform = platform;
    }

}

重命名节点后,序列化生成的Xml文档:

<?xml version="1.0" encoding="utf-8"?>

<ValidUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Platform="Android">

  <RenameUserData>

    <RenameUser UserId="5616851681" UserGender="false">Sylvan</RenameUser>

    <RenameUser UserId="4684646551" UserGender="false">Bob</RenameUser>

    <RenameUser UserId="68416511" UserGender="true">Cathy</RenameUser>

  </RenameUserData>

</ValidUser>

总结:数组和列表在序列化时,默认情况下会根据类型中的数据成员名称生成一个节点, 列表项会生成子节点,如果要重命名,可以使用[XmlArray("example")]、[XmlArrayItem("example")]来实现;

还可以直接用[XmlElement]控制不生成列表的父节点<RenameUserData>,代码如下:

public class ValidUser
{
    [XmlAttribute]
    public string Platform { get; set; }

    [XmlElement("RenameUser")]
    public List<User> validUserData = new List<User> ();

    public ValidUser() { }

    public ValidUser(List<User> data, string platform)
    {
        validUserData = data;
        Platform = platform;
    }
}

重命名节点后,序列化生成的Xml文档:

<?xml version="1.0" encoding="utf-8"?>

<ValidUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Platform="Android">

  <RenameUser UserId="5616851681" UserGender="false">Sylvan</RenameUser>

  <RenameUser UserId="4684646551" UserGender="false">Bob</RenameUser>

  <RenameUser UserId="68416511" UserGender="true">Cathy</RenameUser>

</ValidUser>

类型继承以及混合输出

测试代码:

class Program
{
    private const string filePath = "UserList.xml";

    static void Main(string[] args)
    {
        Developer dl1 = new Developer("Sylvan", "1320486310", false, 25, "C/C++");
        Developer dl2 = new Developer("Dane", "8641864613", true, 24, "C#/JS/Lua");
        Designer dg1 = new Designer("Dane", "8641864613", true, 24, "PS、Maya、Max");

        UsreData userDatas = new UsreData (new List<Person> { dl1,dl2,dg1});

        XmlTools.Serialize(userDatas,filePath);

        Console.ReadKey();
    }
}

测试类:

[XmlType("RenameDeveloper")]
public class Developer : Person {
    [XmlElement("WorkLanguage")]
    public String Languages { get; set; }
    public Developer(string name, string id, bool gender, int age, string language) : base(name, id, gender, age) {
        Languages = language;
    }
    public Developer() : base() { }
    public override string ToString()
    {
        return base.ToString() + ", Work with the languages : " + Languages + " .";
    }
}

[XmlType("RenameDesigner")]
public class Designer : Person {
    [XmlElement("WorkSoftware")]
    public string Software { get; set; }
    public Designer(string name , string id ,bool gender , int age , string software) : base(name,id,gender,age) {
        Software = software;
    }

    public Designer() : base() { }

    public override string ToString()
    {
        return base.ToString() + "Work with software";
    }

}

public class UsreData {
    [XmlArrayItem(typeof(Developer)),
        XmlArrayItem(typeof(Designer))]
    public List<Person> UsersList { get; set; }

    public UsreData() { }

    public UsreData(List<Person> list) => UsersList = list;
}

输出的XMl文档:

<?xml version="1.0" encoding="utf-8"?>

<UsreData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <UsersList>

    <RenameDeveloper>

      <Name>Sylvan</Name>

      <ID>1320486310</ID>

      <Gender>false</Gender>

      <Age>25</Age>

      <WorkLanguage>C/C++</WorkLanguage>

    </RenameDeveloper>

    <RenameDeveloper>

      <Name>Dane</Name>

      <ID>8641864613</ID>

      <Gender>true</Gender>

      <Age>24</Age>

      <WorkLanguage>C#/JS/Lua</WorkLanguage>
    </RenameDeveloper>

    <RenameDesigner>

      <Name>Dane</Name>

      <ID>8641864613</ID>

      <Gender>true</Gender>

      <Age>24</Age>

      <WorkSoftware>PS、Maya、Max</WorkSoftware>

  </UsersList>

</UsreData>

注意事项

XmlSerializer 无法序列化或反序列化 Arrays of ArrayList、 Arrays of List<T>;

可以使用[XmlIgnore]来忽略不需要进行序列化的数据;

可以使用[XmlElement(order = 2)]来调节数据的序列化顺序;

REF

书籍:

深入理解C#、C#高级编程、C#游戏脚本编程

文档:

https://msdn.microsoft.com/zh-cn/library/system.xml(v=vs.110).aspx

https://litjson.net/docs/quickstart/

https://www.newtonsoft.com/json/help/html/Introduction.htm

https://developers.google.com/protocol-buffers/docs/reference/csharp/

https://github.com/mgravell/protobuf-net

博客:

http://www.cnblogs.com/fish-li/archive/2013/05/05/3061816.html

https://www.cnblogs.com/guxia/p/8242483.html

posted @ 2018-06-17 21:49  SylvanYan  阅读(1167)  评论(0编辑  收藏  举报
TOP