序列化
由于指針和引用類型的存在,在運行中的程序中,數(shù)據(jù)不一定是整塊的。
可能東一塊西一塊散落在內(nèi)存的各個地方。
序列,是指連續(xù)且有序的一個整體。序列化就是把數(shù)據(jù)變?yōu)檫B續(xù)有序整體的過程。
經(jīng)過這樣處理后的數(shù)據(jù)就可以方便的進行傳輸和儲存了。
xml序列化
xml格式
xml是一種文本數(shù)據(jù)格式。用節(jié)點樹的形式表示數(shù)據(jù)的名字和數(shù)據(jù)的內(nèi)容。
在c#中,時間,數(shù)字,字符串及其他的基本類型內(nèi)置了直接和字符串進行轉化的方式。
而復雜類型會通過反射拆解他的成員,一直拆解直到只有基本類型為止。
public class Weapon//自帶的序列化api要求類是public的。
{
public (int, int) Attack { get; set; }
public float Speed { get; set; }
public int Level { get; set; }
}
<?xml version="1.0" encoding="utf-16"?>
<Weapon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Attack>
<Item1>10</Item1>
<Item2>20</Item2>
</Attack>
<Speed>1.5</Speed>
<Level>3</Level>
</Weapon>
序列化api
標準庫下只提供了System.Xml.Serialization
命名空間給的序列化api。
這個api不能直接序列化為string類型。只能寫入到流里面。
但是我們可以使用StringWriter
和StringRead
,這兩個把字符串偽裝成流的東西讓他寫入。
Weapon weapon = new Weapon() { Attack = (10, 20), Speed = 1.5f, Level = 3 };
// 創(chuàng)建一個XmlSerializer對象,傳入Person類型
XmlSerializer xs = new XmlSerializer(typeof(Weapon));
// 創(chuàng)建一個StringWriter對象,用于保存XML字符串
StringWriter sw = new StringWriter();
// 調(diào)用XmlSerializer的Serialize方法,把Person對象序列化為XML字符串,并寫入StringWriter對象
xs.Serialize(sw, weapon);
// 從StringWriter對象中獲取XML字符串
string xml = sw.ToString();
// 輸出XML字符串
Console.WriteLine(xml);
為了簡化這個過程,可以制作擴展方法。
public static class Extension
{
/// <summary>
/// 將對象xml序列化為字符串
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
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();
}
/// <summary>
/// 將xml字符串反序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static T XmlDeSerialize<T>(this string value)
{
XmlSerializer xml = new XmlSerializer(typeof(T));
StringReader sr = new StringReader(value);
return (T)xml.Deserialize(sr);
}
}
特性控制序列化規(guī)則
這個序列化api
- 要求目標類必須是具有
public
修飾的。 - 他只會對
public
的成員進行序列化,包括字段和屬性。 - 他要求目標類型有一個公共無參構造器
- 通過反射進行賦值,如果目標屬性沒有set訪問器,或者自己沒有同名元素,目標屬性會保持默認值。
一些特性可以控制他的序列化規(guī)則。
元素
XmlElement
特性可以指定元素名字。
在對數(shù)組或集合使用時,他會被平鋪成元素。
對數(shù)組使用前
<Person>
<Hobbies>
<string>讀書</string>
<string>寫作</string>
<string>編程</string>
</Hobbies>
</Person>
對數(shù)組使用后
<Person>
<Hobbies>讀書</Hobbies>
<Hobbies>寫作</Hobbies>
<Hobbies>編程</Hobbies>
</Person>
屬性
XmlAttribute
特性可以讓一個成員以xml屬性來進行序列化。
這要求他不能是復合類型,必須像int,bool,string這樣可以不拆分直接用字符串表示的類型。
對成員使用前
<Person>
<Age>20</Age>
</Person>
對成員使用后
<Person Age="20" />
文本
XmlText
特性可以讓一個成員成為文本節(jié)點進行序列化。
因為文本節(jié)點沒法進行區(qū)分,所以一個類下最多只能有一個成員具有這個特性。
對成員使用前
<Person>
<Age>20</Age>
</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; }
}
多態(tài)
xml可以表示更多的信息,以至于多態(tài)都可以保存。
對數(shù)組或集合使用XmlArrayItem
指定類型,可以在反序列化的時候識別出類型
(序列化的時候有沒有都會保存類型)。
不過你需要提前預測可能出現(xiàn)的所有類型并一個一個進行指定。
public class Data
{
[XmlArrayItem("字符串", typeof(string))]
[XmlArrayItem("數(shù)字", typeof(int))]
public object[] Datas;
}
更多
請參閱使用屬性控制 XML 序列化
xml樹
對于既存的xml字符串,可以使用System.Xml.Linq
命名空間下的XElement.Parse
進行解析。
對于文件,流之類的東西,可以使用XElement.Load
進行讀取加載(參數(shù)是流或路徑)。
一個XElement實例可以使用ToString
查看他的xml字符串,
可以使用Save
保存為文件或?qū)懭氲搅髦小?/p>
xml節(jié)點
xml樹的內(nèi)容非常多,按照繼承鏈有以下類型。
- XObject
- XAttribute
- XNode
- XComment
- XDocumentType
- XProcessingInstruction
- XText
- XCData
- XContainer
- XDocument
- XElement
<?xml version="1.0" encoding="utf-8"?>
<!-- 這是一個處理指令(XProcessingInstruction),用于聲明 XML 文檔的版本、編碼等信息 -->
<!-- This is a comment -->
<!-- 這是一個注釋(XComment),用于添加一些說明性的文本 -->
<!DOCTYPE Root [
<!-- 這是一個文檔類型聲明(XDocumentType),用于定義 XML 文檔的結構和約束 -->
<!ELEMENT Root (Child1, Child2)>
<!-- 這是一個元素類型聲明,用于指定 Root 元素的內(nèi)容模型 -->
<!ATTLIST Root id ID #REQUIRED>
<!-- 這是一個屬性列表聲明,用于指定 Root 元素的屬性 -->
]>
<Root id="R1">
<!-- 這是一個元素(XElement),表示 XML 文檔的根元素,它有一個屬性(XAttribute) id,值為 R1 -->
<Child1>Some text</Child1>
<!-- 這是一個元素(XElement),表示 Root 元素的第一個子元素,它有一些文本內(nèi)容(XText) -->
<Child2 att="A1"/>
<!-- 這是一個元素(XElement),表示 Root 元素的第二個子元素,它有一個屬性(XAttribute) att,值為 A1 -->
<Child3><![CDATA[在這里可以輸入<>,xml,!!]]></Child3>
<!--這是一個元素(XElement),他里面有一個CData,表示不會被轉義的文本。-->
</Root>
屬性
屬性是在元素上面,以鍵值對形式的東西。
屬性只能保存純文本信息,不能表示有層級關系的內(nèi)容。
XElement xel = new XElement("ele");
Console.WriteLine(xel);
var xat = new XAttribute("name", "張三");
xel.Add(xat);
Console.WriteLine(xel);
<ele />
<ele name="張三" />
從XElement實例上可以調(diào)用Attribute
方法來查詢指定名字的特性。
可以從獲取到的Attribute上修改他,也可以從XElement直接Set指定名字的屬性。
XElement xel2 = XElement.Parse(@"<ele name=""張三"" />");
var xat2=xel2.Attribute("name");
xat2.Value = "999";//Value只能是string類型
Console.WriteLine(xel2);
xel2.SetAttributeValue("name",true);//這個可以是任意類型,如果是null則會刪除這個屬性。
Console.WriteLine(xel2);
Console.WriteLine(xat2);
<ele name="999" />
<ele name="true" />
name="true"
屬性也可以通過強轉轉為字符串,數(shù)字,時間等基礎類型。
XAttribute xat3 = new XAttribute("name", "16");
float? f = (float?)xat3;
Console.WriteLine(f);
基礎類型是指在xml格式中定義了的類型。是xml的基礎類型而不是c#的基礎類型。
注釋
在xml中,使用<!--
和-->
包圍的部分是注釋。注釋內(nèi)不會要求格式。
XComment comment = new XComment("這個是注釋");
Console.WriteLine(comment);
Console.WriteLine("================");
XElement xel = new XElement("root",comment);
Console.WriteLine(xel);
<!--這個是注釋-->
================
<root>
<!--這個是注釋-->
</root>
注釋不保存數(shù)據(jù)信息,所以不能像屬性那樣使用強轉來解析數(shù)據(jù)。
文檔類型
文檔類型是提供文檔節(jié)點驗證的說明。
通常會引用外部文件來要求xml符合格式。
如果內(nèi)聯(lián)進xml樹則如下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<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);
驗證有專門的方法,不會出現(xiàn)在解析的時候出現(xiàn)異常。
不過如何驗證我沒查到。
處理命令
處理命令是<?
到?>
之間的內(nèi)容。
緊跟隨左邊的<?
的文本會被解析為目標(Target)。
然后一個空格之后的所有內(nèi)容(即便仍然有空格拆分多塊)全部為數(shù)據(jù)(Data)。
XElement note = XElement.Parse(@"
<場景>
<對話 text=""您想要什么?"">
<回答 text=""紅寶石"">
<?屬性 攻擊力 1?>
</回答>
<回答 text=""藍寶石"">
<?屬性 防御力 1?>
</回答>
<回答 text=""血瓶"">
<?屬性 生命 100?>
</回答>
</對話>
</場景>
");
var p1 = (XProcessingInstruction)note.Elements().First().Elements().First().FirstNode;
Console.WriteLine(p1.Target);//屬性
Console.WriteLine(p1.Data);//攻擊力 1
文本
在元素下的文本為文本節(jié)點。
XElement note = XElement.Parse(@"
<root>
你好,世界
</root>
");
var t1 =(XText) note.FirstNode;
Console.WriteLine(t1.Value);
但是只要沒有貼合元素的開閉標簽,都會包含。
例如這里的標簽和文字之間有換行。所以這里的文本內(nèi)容會包含換行符。
轉義文本
通常情況下,xml內(nèi)容是不能包含尖括號,引號之類的東西。如果要書寫則需要轉義。
但是如果是文本元素,可以通過<![CDATA[]]>
聲明轉義文本。
因為他的開啟和結束符號很多。所以通常請看下,里面的東西都不會有歧義的解讀。
XElement note = XElement.Parse(@"
<root>
<![CDATA[這是一個xml的標簽:<a int=""3"" />]]>
</root>
");
var t1 =(XCData) note.FirstNode;
Console.WriteLine(t1.Value);
這個節(jié)點只有可能是文本節(jié)點,所以這個類型是從XText
派生的。
文檔
文檔和元素的共同基類XContainer
是指里面可能嵌套東西的。
只是用來解析一個元素,使用文檔和元素都可以。
但是文檔另外可能包含xml開頭的說明符。<?xml version="1.0" encoding="UTF-8"?>
這表示使用1.0語法的xml,使用utf-8編碼。
XDeclaration declaration = new XDeclaration("1.0", "UTF-8", null);
元素
xml元素是xml樹中最重要的東西,可以表示層級,可以包含內(nèi)容,可以攜帶屬性。
一個元素可以嵌套多個同名元素,所以不像json可以使用索引器訪問內(nèi)容。
以下示例展示xml元素的常用方法。
XElement letters = XElement.Parse(@"
<letters>
<letter from=""張三"" to=""李四"" date=""2022-01-30"">
<subject>問候</subject>
<body>李四,你好!最近過得怎么樣?</body>
</letter>
<letter from=""李四"" to=""張三"" date=""2022-02-01"">
<subject>回復</subject>
<body>張三,你好!我最近很好,謝謝你的關心。</body>
</letter>
</letters>
");
foreach (var item in letters.Descendants().Where(x=>x.Name=="subject"))
{//Descendants方法可以遞歸獲取所有子節(jié)點。
Console.WriteLine(item.Value);
}
Console.WriteLine(letters.Elements("letter").First().Value);
//Elements方法為獲取所有指定名字的直屬子節(jié)點??梢圆惶蠲?。
letters.SetAttributeValue("count",4);
//SetAttributeValue方法可以修改屬性的值,如果沒有這個屬性會添加。如果使用null值會移除屬性。
元素可以使用Add
方法,或者在構造器的名字后面加入多個值。
- 如果是XObject家族的,會被嵌入進樹里面。
- 對于數(shù)組,可迭代類型的東西,會拆開后對里面的元素依次添加。
- 其他類型的數(shù)據(jù),會使用ToString轉文字后加入進去。所以復雜類型需要自己先序列化,然后解析為xml節(jié)點,再添加xml節(jié)點。
XName
雖然在上面的例子里面,屬性和節(jié)點的名字都是直接使用字符串類型。
但實際上構造器接受的是XName類型,他細分為命名空間和名字。
XElement xel = new XElement("{火蜥蜴戰(zhàn)隊}張三");
Console.WriteLine(xel);
//<張三 xmlns="火蜥蜴戰(zhàn)隊" />
XName xn = xel.Name;
Console.WriteLine(xn.NamespaceName); //火蜥蜴戰(zhàn)隊
Console.WriteLine(xn.LocalName); //張三
XName類型的構造器是私有的。但是有一個從string而來的隱式轉換。
在if語句中可以直接使用==
進行判斷,
但是在switch語句中,需要從他的屬性里訪問出他的名字。
因為隱式轉換是一個操作過程,不屬于常量,不能用于switch。
一個節(jié)點有明明空間時,他的子節(jié)點默認和他是相同的命名空間。
所以命名空間不同的都要標識,包括命名空間為""
的。文章來源:http://www.zghlxwxcb.cn/news/detail-563383.html
XElement xel = new XElement("{火蜥蜴戰(zhàn)隊}張三"
, new XElement("李四")
, new XElement("{不死鳥戰(zhàn)隊}王五")
, new XElement("{火蜥蜴戰(zhàn)隊}趙六"));
Console.WriteLine(xel);
/*
<張三 xmlns="火蜥蜴戰(zhàn)隊">
<李四 xmlns="" />
<王五 xmlns="不死鳥戰(zhàn)隊" />
<趙六 />
</張三>
*/
用于屬性上時,會再額外聲明一個命名空間屬性。
這個額外的命名空間屬性是可以更改的。文章來源地址http://www.zghlxwxcb.cn/news/detail-563383.html
XElement xel = new XElement("{火蜥蜴戰(zhàn)隊}張三"
, new XAttribute("{2023}職務","隊長")
, new XElement("李四"
,new XAttribute("{2023}職務","副隊長")
)
);
Console.WriteLine(xel);
/*
<張三 p1:職務="隊長" xmlns:p1="2023" xmlns="火蜥蜴戰(zhàn)隊">
<李四 p1:職務="副隊長" xmlns="" />
</張三>
*/
Console.WriteLine("===========");
xel.SetAttributeValue(XNamespace.Xmlns + "zhang", "2023");
//XNamespace.Xmlns 這個靜態(tài)變量是聲明命名空間的xml命名空間。和一個字符串相加以后會變成XName
Console.WriteLine(xel);
/*
<張三 zhang:職務="隊長" xmlns:zhang="2023" xmlns="火蜥蜴戰(zhàn)隊">
<李四 zhang:職務="副隊長" xmlns="" />
</張三>
*/
到了這里,關于c#示例-xml序列化和xml樹的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!