Unity:XML笔记(二)——Xml序列化、反序列化、IXmlSerializable接口

发布于:2025-09-07 ⋅ 阅读:(19) ⋅ 点赞:(0)

写在前面:

写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。

三、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);
        }
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到