由于指针和引用类型的存在,在运行中的程序中,数据不一定是整块的。
可能东一块西一块散落在内存的各个地方。
序列,是指连续且有序的一个整体。序列化就是把数据变为连续有序整体的过程。
经过这样处理后的数据就可以方便的进行传输和储存了。
xml是一种文本数据格式。用节点树的形式表示数据的名字和数据的内容。
在c#中,时间,数字,字符串及其他的基本类型内置了直接和字符串进行转化的方式。
而复杂类型会通过反射拆解他的成员,一直拆解直到只有基本类型为止。
public class Weapon//自带的序列化api要求类是public的。
{
public (int, int) Attack { get; set; }
public float Speed { get; set; }
public int Level { get; set; }
}
<Weapon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Attack>
<Item1>10Item1>
<Item2>20Item2>
Attack>
<Speed>1.5Speed>
<Level>3Level>
Weapon>
标准库下只提供了System.Xml.Serialization
命名空间给的序列化api。
这个api不能直接序列化为string类型。只能写入到流里面。
但是我们可以使用StringWriter
和StringRead
,这两个把字符串伪装成流的东西让他写入。
Weapon weapon = new Weapon() { Attack = (10, 20), Speed = 1.5f, Level = 3 };
// 创建一个XmlSerializer对象,传入Person类型
XmlSerializer xs = new XmlSerializer(typeof(Weapon));
// 创建一个StringWriter对象,用于保存XML字符串
StringWriter sw = new StringWriter();
// 调用XmlSerializer的Serialize方法,把Person对象序列化为XML字符串,并写入StringWriter对象
xs.Serialize(sw, weapon);
// 从StringWriter对象中获取XML字符串
string xml = sw.ToString();
// 输出XML字符串
Console.WriteLine(xml);
为了简化这个过程,可以制作扩展方法。
public static class Extension
{
///
/// 将对象xml序列化为字符串
///
///
///
///
public static string XmlSerialize<T>(this T value)
{
XmlSerializer xml = new XmlSerializer(typeof(T));
StringWriter sw = new StringWriter();
xml.Serialize(sw, value);
return sw.ToString();
}
///
/// 将xml字符串反序列化
///
///
///
///
public static T XmlDeSerialize<T>(this string value)
{
XmlSerializer xml = new XmlSerializer(typeof(T));
StringReader sr = new StringReader(value);
return (T)xml.Deserialize(sr);
}
}
这个序列化api
public
修饰的。public
的成员进行序列化,包括字段和属性。一些特性可以控制他的序列化规则。
XmlElement
特性可以指定元素名字。
在对数组或集合使用时,他会被平铺成元素。
对数组使用前
<Person>
<Hobbies>
<string>读书string>
<string>写作string>
<string>编程string>
Hobbies>
Person>
对数组使用后
<Person>
<Hobbies>读书Hobbies>
<Hobbies>写作Hobbies>
<Hobbies>编程Hobbies>
Person>
XmlAttribute
特性可以让一个成员以xml属性来进行序列化。
这要求他不能是复合类型,必须像int,bool,string这样可以不拆分直接用字符串表示的类型。
对成员使用前
<Person>
<Age>20Age>
Person>
对成员使用后
<Person Age="20" />
XmlText
特性可以让一个成员成为文本节点进行序列化。
因为文本节点没法进行区分,所以一个类下最多只能有一个成员具有这个特性。
对成员使用前
<Person>
<Age>20Age>
Person>
对成员使用后
<Person>
20
Person>
带有XmlIgnore
特性的成员在序列化和反序列化中会被无视。
属性和元素的特性,可以对属性或元素命名。
此外,元素具有可选的Order属性,这个属性可以控制序列化的顺序。
但是要么全都没有这个属性,要么全部显式声明这个属性。
public class Person
{
[XmlElement("姓名",Order =1)]
public string Name { get; set; }
[XmlAttribute("年龄")]
public int Age { get; set; }
[XmlElement(Order =0)]
public string[] Hobbies { get; set; }
}
xml可以表示更多的信息,以至于多态都可以保存。
对数组或集合使用XmlArrayItem
指定类型,可以在反序列化的时候识别出类型
(序列化的时候有没有都会保存类型)。
不过你需要提前预测可能出现的所有类型并一个一个进行指定。
public class Data
{
[XmlArrayItem("字符串", typeof(string))]
[XmlArrayItem("数字", typeof(int))]
public object[] Datas;
}
请参阅使用属性控制 XML 序列化
对于既存的xml字符串,可以使用System.Xml.Linq
命名空间下的XElement.Parse
进行解析。
对于文件,流之类的东西,可以使用XElement.Load
进行读取加载(参数是流或路径)。
一个XElement实例可以使用ToString
查看他的xml字符串,
可以使用Save
保存为文件或写入到流中。
xml树的内容非常多,按照继承链有以下类型。
DOCTYPE Root [
<!ELEMENT Root (Child1, Child2)>
<!ATTLIST Root id ID #REQUIRED>
]>
<Root id="R1">
<Child1>Some textChild1>
<Child2 att="A1"/>
<Child3>,xml,!!]]>Child3>
Root>
属性是在元素上面,以键值对形式的东西。
属性只能保存纯文本信息,不能表示有层级关系的内容。
XElement xel = new XElement("ele");
Console.WriteLine(xel);
var xat = new XAttribute("name", "张三");
xel.Add(xat);
Console.WriteLine(xel);
从XElement实例上可以调用Attribute
方法来查询指定名字的特性。
可以从获取到的Attribute上修改他,也可以从XElement直接Set指定名字的属性。
XElement xel2 = XElement.Parse(@" ");
var xat2=xel2.Attribute("name");
xat2.Value = "999";//Value只能是string类型
Console.WriteLine(xel2);
xel2.SetAttributeValue("name",true);//这个可以是任意类型,如果是null则会删除这个属性。
Console.WriteLine(xel2);
Console.WriteLine(xat2);
name="true"
属性也可以通过强转转为字符串,数字,时间等基础类型。
XAttribute xat3 = new XAttribute("name", "16");
float? f = (float?)xat3;
Console.WriteLine(f);
基础类型是指在xml格式中定义了的类型。是xml的基础类型而不是c#的基础类型。
在xml中,使用包围的部分是注释。注释内不会要求格式。
XComment comment = new XComment("这个是注释");
Console.WriteLine(comment);
Console.WriteLine("================");
XElement xel = new XElement("root",comment);
Console.WriteLine(xel);
================
注释不保存数据信息,所以不能像属性那样使用强转来解析数据。
文档类型是提供文档节点验证的说明。
通常会引用外部文件来要求xml符合格式。
如果内联进xml树则如下。
DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Toveto>
<from>Janifrom>
<heading>Reminderheading>
<body>Don't forget me this weekend!body>
note>
他要求note元素必须包含to,from,heading,body元素。
然后to,from,heading,body都必须是可解析的文字(不能包含嵌套的元素)。
获取文档约束需要通过文档进行解析来获取。
XDocument xDoc = XDocument.Parse(xml);//xml替换为上述的字符串
var xdt = xDoc.DocumentType;
Console.WriteLine(xdt.InternalSubset);
验证有专门的方法,不会出现在解析的时候出现异常。
不过如何验证我没查到。
处理命令是到
?>
之间的内容。
紧跟随左边的的文本会被解析为目标(Target)。
然后一个空格之后的所有内容(即便仍然有空格拆分多块)全部为数据(Data)。
XElement note = XElement.Parse(@"
<场景>
<对话 text=""您想要什么?"">
<回答 text=""红宝石"">
回答>
<回答 text=""蓝宝石"">
回答>
<回答 text=""血瓶"">
回答>
对话>
场景>
");
var p1 = (XProcessingInstruction)note.Elements().First().Elements().First().FirstNode;
Console.WriteLine(p1.Target);//属性
Console.WriteLine(p1.Data);//攻击力 1
在元素下的文本为文本节点。
XElement note = XElement.Parse(@"
你好,世界
");
var t1 =(XText) note.FirstNode;
Console.WriteLine(t1.Value);
但是只要没有贴合元素的开闭标签,都会包含。
例如这里的标签和文字之间有换行。所以这里的文本内容会包含换行符。
通常情况下,xml内容是不能包含尖括号,引号之类的东西。如果要书写则需要转义。
但是如果是文本元素,可以通过声明转义文本。
因为他的开启和结束符号很多。所以通常请看下,里面的东西都不会有歧义的解读。
XElement note = XElement.Parse(@"
]]>
");
var t1 =(XCData) note.FirstNode;
Console.WriteLine(t1.Value);
这个节点只有可能是文本节点,所以这个类型是从XText
派生的。
文档和元素的共同基类XContainer
是指里面可能嵌套东西的。
只是用来解析一个元素,使用文档和元素都可以。
但是文档另外可能包含xml开头的说明符。
这表示使用1.0语法的xml,使用utf-8编码。
XDeclaration declaration = new XDeclaration("1.0", "UTF-8", null);
xml元素是xml树中最重要的东西,可以表示层级,可以包含内容,可以携带属性。
一个元素可以嵌套多个同名元素,所以不像json可以使用索引器访问内容。
以下示例展示xml元素的常用方法。
XElement letters = XElement.Parse(@"
问候
李四,你好!最近过得怎么样?
回复
张三,你好!我最近很好,谢谢你的关心。
");
foreach (var item in letters.Descendants().Where(x=>x.Name=="subject"))
{//Descendants方法可以递归获取所有子节点。
Console.WriteLine(item.Value);
}
Console.WriteLine(letters.Elements("letter").First().Value);
//Elements方法为获取所有指定名字的直属子节点。可以不填名字。
letters.SetAttributeValue("count",4);
//SetAttributeValue方法可以修改属性的值,如果没有这个属性会添加。如果使用null值会移除属性。
元素可以使用Add
方法,或者在构造器的名字后面加入多个值。
虽然在上面的例子里面,属性和节点的名字都是直接使用字符串类型。
但实际上构造器接受的是XName类型,他细分为命名空间和名字。
XElement xel = new XElement("{火蜥蜴战队}张三");
Console.WriteLine(xel);
//<张三 xmlns="火蜥蜴战队" />
XName xn = xel.Name;
Console.WriteLine(xn.NamespaceName); //火蜥蜴战队
Console.WriteLine(xn.LocalName); //张三
XName类型的构造器是私有的。但是有一个从string而来的隐式转换。
在if语句中可以直接使用==
进行判断,
但是在switch语句中,需要从他的属性里访问出他的名字。
因为隐式转换是一个操作过程,不属于常量,不能用于switch。
一个节点有明明空间时,他的子节点默认和他是相同的命名空间。
所以命名空间不同的都要标识,包括命名空间为""
的。
XElement xel = new XElement("{火蜥蜴战队}张三"
, new XElement("李四")
, new XElement("{不死鸟战队}王五")
, new XElement("{火蜥蜴战队}赵六"));
Console.WriteLine(xel);
/*
<张三 xmlns="火蜥蜴战队">
<李四 xmlns="" />
<王五 xmlns="不死鸟战队" />
<赵六 />
张三>
*/
用于属性上时,会再额外声明一个命名空间属性。
这个额外的命名空间属性是可以更改的。
XElement xel = new XElement("{火蜥蜴战队}张三"
, new XAttribute("{2023}职务","队长")
, new XElement("李四"
,new XAttribute("{2023}职务","副队长")
)
);
Console.WriteLine(xel);
/*
<张三 p1:职务="队长" xmlns:p1="2023" xmlns="火蜥蜴战队">
<李四 p1:职务="副队长" xmlns="" />
张三>
*/
Console.WriteLine("===========");
xel.SetAttributeValue(XNamespace.Xmlns + "zhang", "2023");
//XNamespace.Xmlns 这个静态变量是声明命名空间的xml命名空间。和一个字符串相加以后会变成XName
Console.WriteLine(xel);
/*
<张三 zhang:职务="队长" xmlns:zhang="2023" xmlns="火蜥蜴战队">
<李四 zhang:职务="副队长" xmlns="" />
张三>
*/