写在前面:
写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。
三、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);
}
}
}