寫在前面:
寫本系列(自用)的目的是回顧已經學過的知識、記錄新學習的知識或是記錄心得理解,方便自己以后快速復習,減少遺忘。
三、Xml序列化
序列化就是把想要存儲的內容轉換為字節序列用于存儲或傳遞。
1、序列化
我們先創建一個類,之后利用Xml序列化來存儲這個類:
public class Test
{public int testPublic = 10;private int testPrivate = 11;protected int testProtected = 12;internal int testInternal = 13;public string testPublicStr = "123";public int testPro { get; set; }public Test2 testClass = new Test2();public int[] arrayInt = new int[3] { 5, 6, 7 };public List<int> listInt = new List<int>() { 1, 2, 3, 4 };public List<Test2> listItem = new List<Test2>() { new Test2(), new Test2() };
}public class Test2
{public int test1 = 1;public float test2 = 1.1f;public bool test3 = true;
}
Xml序列化的第一步是確認存儲路徑:string path = Application.persistentDataPath + "/Test.xml";該存儲路徑設置方式和之前使用XmlDocument存儲的方式一樣。
序列化存儲需要在一個using代碼塊中,如下:
using (StreamWriter stream = new StreamWriter(path)){? ? ? ? ? }
現在對這行代碼進行解釋。StreamWriter是向文件流寫入字符的類,屬于System.IO命名空間。括號內的代碼的意思是:寫入一個文件流,如果有該文件,直接打開并修改;如果沒有該文件,自動釋放掉。new StreamWriter()
這里還涉及到using 的新用法:括號內包裹的聲明的對象,會在大括號語句塊結束后自動釋放掉。當語句塊結束時會自動調用對象的Dispose方法,讓其銷毀。using一般是配合 內存占用比較大或者有讀寫操作時進行使用。
在語句塊中,我們需要創建一個“序列化機器”來將我們的類序列化:
?XmlSerializer s = new XmlSerializer(typeof(Test));
需要注意的是,序列化機器的類型,一定是要和我們需要序列化存儲的對象是同樣的類型。接下來就可以使用序列化機器進行序列化:
s.Serialize(stream, lt);
這句代碼通過序列化機器,對我們類對象進行翻譯,將其翻譯成xml文件寫入到對應文件中。第一個參數:文件流對象;第二個參數:想要被翻譯的對象。這樣,就完成了序列化存儲:
public class lession1 : MonoBehaviour
{void Start(){Test lt = new Test();string path = Application.persistentDataPath + "/Test.xml";using (StreamWriter stream = new StreamWriter(path)){XmlSerializer s = new XmlSerializer(typeof(Test));s.Serialize(stream, lt);}}
}
我們可以在我們設置的保存路徑中,找到序列化后的Xml文件:
<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><testPublic>10</testPublic><testPublicStr>123</testPublicStr><testClass><test1>1</test1><test2>1.1</test2><test3>true</test3></testClass><arrayInt><int>5</int><int>6</int><int>7</int></arrayInt><listInt><int>1</int><int>2</int><int>3</int><int>4</int></listInt><listItem><Test2><test1>1</test1><test2>1.1</test2><test3>true</test3></Test2><Test2><test1>1</test1><test2>1.1</test2><test3>true</test3></Test2></listItem><testPro>0</testPro>
</Test>
可以看到,我們類中private、protected、internal修飾的變量都沒有被序列化。也就是說,Xml序列化方法只能序列化公共成員。此外,不支持字典序列化,如果類中有字典,就會報錯。
2、修改節點信息或設置屬性信息
可以通過特性修改節點信息或者設置屬性信息。例如,如果需要將Test2類中的成員以屬性方式存儲,可以加上特性:[XmlAttribute()],如果想修改屬性的名字,可以在括號內傳入想要的屬性名:[XmlAttribute("Test1")。
public class Test2
{[XmlAttribute("Test1")]public int test1 = 1;[XmlAttribute()]public float test2 = 1.1f;[XmlAttribute()]public bool test3 = true;
}
如果想要修改變量的名字,可以加上特性:[XmlElement("testPublic111")],括號內傳入屬性名:
[XmlElement("testPublic111")]
public int testPublic = 10;
如果想要修改數組的名字,可以添加特性:[XmlArray("IntList")],如果想要修改數組中元素節點的名字,可以用特性:[XmlArrayItem("Int32")]
[XmlArray("IntList")]
[XmlArrayItem("Int32")]
public int[] arrayInt = new int[3] { 5, 6, 7 };
四、Xml反序列化
反序列化就是把存儲或收到的字節序列信息解析讀取出來使用。
1、判斷文件是否存在
在反序列化之前,需要判斷文件是否存在。判斷方式是:File.Exists(path),括號中傳入的是文件地址。
void Start()
{string path = Application.persistentDataPath + "/Test.xml";if(File.Exists(path)){}
}
2、反序列化
反序列化和序列化基本相同,區別就是這里使用的是StreamReader,從流中讀出字符的類。如下:using (StreamReader reader = new StreamReader(path)){? }
同樣的,在using語句塊中,初始化一個反序列化翻譯機器:?XmlSerializer s = new XmlSerializer(typeof(Test));
然后調用s.Deserialize(reader)方法即可完成反序列化:
void Start()
{string path = Application.persistentDataPath + "/Test.xml";if(File.Exists(path)){using (StreamReader reader = new StreamReader(path)){XmlSerializer s = new XmlSerializer(typeof(Test));Test lt = s.Deserialize(reader) as Test;}}
}
這里需要注意的是,在三中定義Test類時,為了方便,很多值都是直接在類中初始化的。對于List對象,如果有默認值,反序列化時不會清空而是會往后繼續添加,所以最好不要在類中直接初始化。
五、IXmlSerializable接口
C#的的XmlSerializer提供了可擴展內容,可以讓一些不能被序列化和反序列化的特殊類能被處理,例如字典。讓特殊類繼承 IXmlSerializable接口實現其中的方法即可。
1、自定義序列化和反序列化
先按三、四所學知識創建一個類并書寫序列化和反序列化方法:
public class Test3
{public int test1;public string test2;
}public class lession3 : MonoBehaviour
{void Start(){Test3 t = new Test3();string path = Application.persistentDataPath + "/Test3.xml";using (StreamWriter writer = new StreamWriter(path)){XmlSerializer s = new XmlSerializer(typeof(Test3));s.Serialize(writer, t);}using(StreamReader reader = new StreamReader(path)){XmlSerializer s = new XmlSerializer(typeof(Test3));Test3 t2 = s.Deserialize(reader) as Test3;}}
}
這段代碼實現了對類Test3進行序列化和反序列化。這里補充一點,在序列化時 如果對象中的引用成員為空 那么xml里面是看不到該字段的,所以這里的xml文件中沒有string。
接下來,我們就可以開始自定義序列化方法和反序列化方法。首先需要類Test3繼承IXmlSerializable接口,并在Test3中重寫接口中的函數:
其中public XmlSchema GetSchema()暫時不需要了解,該函數返回結構,直接return null即可。public void ReadXml(XmlReader reader)是反序列化會調用的方法,在其中書寫的代碼能夠替換掉該類原來的反序列化函數。public void WriteXml(XmlWriter writer)是序列化會調用的方法。這兩個函數可以定義序列化的規則。
public class Test3:IXmlSerializable
{public int test1;public string test2;//返回結構public XmlSchema GetSchema(){return null;}//反序列化時會自動調用方法public void ReadXml(XmlReader reader){}//序列化時會自動調用的方法public void WriteXml(XmlWriter writer){}
}
(1)自定義讀屬性和寫屬性
首先來自定義讀屬性和寫屬性的規則。如果要自定義序列化的規則,一定會用到XmlWriter、XmlReader中的一些方法。
對于寫屬性,XmlWriter是寫入器對象提供一系列方法來生成和寫入 XML 格式的數據。可以使用:writer.WriteAttributeString()寫入屬性,括號內傳入的第一個參數是屬性名,第二個參數是屬性的內容。
對于讀屬性,XmlReader則是用于讀入數據的工具類。可以通過reader["test1"]來獲得[]內屬性的值。如下所示:
public void ReadXml(XmlReader reader)
{this.test1 = int.Parse(reader["test1"]);this.test2 = reader["test2"];
}public void WriteXml(XmlWriter writer)
{writer.WriteAttributeString("test1", this.test1.ToString());writer.WriteAttributeString("test2", this.test2)
}
(2)自定義讀節點和寫節點
①方式1
寫節點可以通過XmlWriter的方法:writer.WriteElementString(),括號內分別傳入節點名、節點的數值即可。
讀節點需要用reader.Read(),表示逐步讀。一開始Reader位于根節點Test3,調用reader.Read()后讀到test1節點,繼續調用reader.Read()后讀到test1節點包裹的內容,此時就可以將該值讀出來。繼續調用reader.Read()后讀到尾部包裹節點,再調用reader.Read()讀到test2節點...以此類推。
? 這里為了方便看所以給test2賦值為了123再進行讀寫數據。
public void ReadXml(XmlReader reader)
{reader.Read();//這時是讀到的test1節點reader.Read();//這時是讀到的test1節點包裹的內容this.test1 = int.Parse(reader.Value);reader.Read();//尾部包裹節點reader.Read();//這時讀到的是test2節點reader.Read();//讀到的是test2節點包裹的內容this.test2 = reader.Value;
}public void WriteXml(XmlWriter writer)
{writer.WriteElementString("test1", this.test1.ToString());writer.WriteElementString("test2", this.test2);
}
②方式2
方式①讀節點的重復代碼太多了,可以采用方式2這種寫法:
while(reader.Read())
{if(reader.NodeType == XmlNodeType.Element){switch(reader.Name){case "test1":reader.Read();this. test1 = int.Parse(reader.Value);break;case "tese2":reader.Read();this.test2 = reader.Value;break;}}
}
(3)自定義讀寫包裹節點
如果想自定義寫包裹節點,類似于下圖,test1中包裹著節點int,test2中包裹著節點string
①寫
以第一個寫int為例。需要先聲明一個序列化器
XmlSerializer s = new XmlSerializer(typeof(int));
然后使用節點相關API:這兩句代碼表示開始節點test1,結束節點test1,在這兩行中間定義的節點就會被包裹在節點test1中間。
writer.WriteStartElement("test1");writer.WriteEndElement();
例如,在以上兩句代碼中間使用序列化器寫入節點teat的值,序列化器是int類型的,所以會生成<int>0</int>節點:
writer.WriteStartElement("test1");
s.Serialize(writer, test1);
writer.WriteEndElement();
②讀
同樣以讀int為例,首先需要創建一個序列化器:
XmlSerializer s = new XmlSerializer(typeof(int));
調用reader.Read()讓reader指向test1:
reader.Read();
然后可以使用以下兩句代碼,表示讀test1節點的開始于結束:
reader.ReadStartElement("test1");reader.ReadEndElement();
最后在中間寫需要讀入的數據即可:
reader.ReadStartElement("test1");
test1 = (int)s.Deserialize(reader);
reader.ReadEndElement();
完整代碼:
public void ReadXml(XmlReader reader)
{XmlSerializer s = new XmlSerializer(typeof(int));reader.Read();reader.ReadStartElement("test1");test1 = (int)s.Deserialize(reader);reader.ReadEndElement();XmlSerializer s2 = new XmlSerializer(typeof(string));reader.ReadStartElement("test2");test2 = (string)s2.Deserialize(reader);reader.ReadEndElement();
}public void WriteXml(XmlWriter writer)
{XmlSerializer s = new XmlSerializer(typeof(int));writer.WriteStartElement("test1");s.Serialize(writer, test1);writer.WriteEndElement();XmlSerializer s2 = new XmlSerializer(typeof(string));writer.WriteStartElement("test2");s2.Serialize(writer, test2);writer.WriteEndElement();
}
2、讓Dictionary支持序列化反序列化
相當于以上知識的一個小應用,可以拓展一個可以被序列化和反序列化的字典類,所以不多解釋:
public class SerializerDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{public XmlSchema GetSchema(){return null;}public void ReadXml(XmlReader reader){XmlSerializer keySer = new XmlSerializer(typeof(TKey));XmlSerializer ValueSer = new XmlSerializer(typeof(TValue));reader.Read();while(reader.NodeType != XmlNodeType.EndElement){TKey key = (TKey)keySer.Deserialize(reader);TValue value = (TValue)ValueSer.Deserialize(reader);this.Add(key, value);}reader.Read();}public void WriteXml(XmlWriter writer){XmlSerializer keySer = new XmlSerializer(typeof(TKey));XmlSerializer ValueSer = new XmlSerializer(typeof(TValue));foreach(KeyValuePair<TKey, TValue> kv in this){keySer.Serialize(writer, kv.Key);ValueSer.Serialize(writer, kv.Value);}}
}