前言:
最近小匹夫參與的遊戲項目到了需要讀取資料的階段了,那麼覺得自己業餘時間也該實踐下資料相關的内容。那麼從哪入手呢?因為用的是Unity3d的遊戲引擎,思來想去就選擇了C#讀取XML檔案這個小功能。網上的例子倒也不少,但總是覺得缺點什麼。比如讀取xml檔案之後該如何處理?看到的文章基本上都是手動建立一個目标類的執行個體,然後手動從讀取的XML檔案的内容中給剛才建立的目标類執行個體相關字段指派。缺點什麼呢?對嘞,感覺上不夠簡單和智能。
正所謂驅動科技發展的原因就是懶,為了使我們的小工具能夠傻瓜到隻需要指定一個需要的目标類型和要讀取的xml的位址就能實作目标類執行個體的動态生成,下面的文字就誕生了。
需要解決的問題:
問,從xml檔案到需要的目标類執行個體需要幾步?
答,讀取XML檔案,執行個體化一個目标執行個體,指派。
問題一:如何讀取XML檔案
是以第一個問題就是如何讀取XML檔案,參考這篇部落格《c#讀取XML》,我們可知備選答案無非如下幾種:
- XmlDocument
- XmlTextReader
- Linq to Xml
1.XmlDocument的使用:
//XmlDocument使用
XmlDocument doc = new XmlDocument();
doc.Load("./Assets/xml-to-egg/xml-to-egg-test/Test.xml");
XmlNode root = doc.SelectSingleNode("Test");
...
但是要注意的是,XmlDocument是讀取整個XML的,是以如果XML内容過多,則會消費很多記憶體。是以XML内容過大時,不推薦使用XmlDocument。
2.XmlTextReader的使用:
//XmlTestReader的使用方法
XmlTextReader reader = new XmlTextReader("./Assets/xml-to-egg/xml-to-egg-test/Test.xml");
//使用read()方法向下讀取
while (reader.Read())
{
.....
}
要說明與XmlDocument的最大差別,其實也很簡單,XmlReader使用Steam(流)來讀取檔案,是以不會對記憶體造成太大的消耗。XmlReader通過read()方法不斷向下讀取,我們就可以在這個過程中進行我們需要的操作。不過這個也不是我們的答案,我們選擇的答案在下面。
3.Linq to Xml
在System.Xml.Linq命名空間中,操作十分簡單和友善。
//Linq to Xml的使用
XElement xml = XElement.Load("./Assets/xml-to-egg/xml-to-egg-test/Test.xml");
//讀取的xml檔案的元素都在生成的XElement的執行個體xml.Elements中。
string name = xml.Element("name").Value;
......
可見十分簡單明了。傳入xml檔案的路徑就會傳回一個XElement類型的執行個體,并且xml檔案的元素也都存入了XElement執行個體中。那麼我們讀取XML檔案的任務就交給它了。
讀取XML相關邏輯的代碼如下:
/// <summary>
/// Sets the xml path.
/// </summary>
public static void SetXmlPath(string p)
{
path = p;
}
/// <summary>
/// Loads the XML Files.
/// </summary>
private static XElement LoadXML()
{
if(path == null)
return null;
XElement xml = XElement.Load(path);
return xml;
}
問題二:如何執行個體化一個目标執行個體。
假設我們并不知道我們的這個動态讀取XML建立執行個體并指派的小工具要處理的是什麼類型的對象,那問題就來了,總不能每一個不同的類都對應一套處理方法吧?那也太不智能且代碼太難以複用了。是以這裡我們執行個體化一個目标執行個體碰到的第一個問題就來了,也就是如何破解目标類型的問題?
答案是使用泛型。
在執行個體化具體對象的時候,才确定類型,這樣就可以避免由于類型不同而導緻的代碼無法複用的問題。
那麼,下面我們的小工具---XMLToEgg就要出場了,對,就是一個處理引用類型的泛型類。
public static class XmlToEgg<T> where T : class
{
}
可是光解決了執行個體類型的問題還是差一步啊,差點什麼呢?對啊,那就是如何執行個體化一個泛型目标執行個體。這也就是我們在執行個體化一個目标執行個體時遇到的第二個問題。
答案是使用反射。
那下面繼續上代碼:
/// <summary>
/// Creates the class initiate.
/// </summary>
private static void CreateInitiate()
{
Type t = typeof(T);
ConstructorInfo ct = t.GetConstructor(System.Type.EmptyTypes);
target = (T)ct.Invoke(null);
}
當然這裡小匹夫假設我們的目标類的構造函數是不需要參數的,如果需要參數也很簡單,看官們自己可以查到這裡就不贅述了。
好了,到這裡我們如何建立一個一開始我們不知道是什麼類型,隻有到建立的時候才知道是什麼東西的類的執行個體的問題就解決了。(好繞)
問題三:如何為建立好的執行個體中的字段指派
終于來到了我們的終極問題,也是我們最終的目标,實作從XML到目标類執行個體的最後一步。在問題二的時候已經說了,作為一個可以複用的工具,對處理的目标類型應該有包容性,那麼既然連目标類型都不确定,那麼目标類型的字段咋能确定呢?是以這個問題的本質其實就是我不知道目标類有啥字段啊。。。(如果你把字段寫死,是不是就沒有一點擴充性了。。。low爆有木有),那問題連環一個接一個,我既然不知道目标類有啥字段,那我更不可能知道目标類的字段的類型了吧。好,就算我啥都知道,我應該怎麼設呢?直接用instance.field = XXX? 圖樣圖森破。
是以問題的本質是明确的:
- 我不知道目标類有啥字段
- 我不知道各個字段是啥類型
- 就算1,2我都知道,但是我就是不知道咋把值賦給相應字段。
正所謂“車到山前必有路,答案還是用反射”。隻要能解決上面三個小問題,那麼最後這一步就算是邁過去了。話不多說,下面上代碼:
/// <summary>
/// attribute assignment,
/// 由于反射中設定字段值的方法會涉及到指派的目标類型和目前類型的轉化,
/// 是以需要使用Convert.ChangeType進行類型轉化
/// </summary>
public static T ToEgg()
{
if(target != null)
{
target = null;
}
CreateInitiate();
XElement xml = LoadXML();
Type t = target.GetType();
FieldInfo[] fields = t.GetFields();
string fieldName = string.Empty;
foreach(FieldInfo f in fields)
{
fieldName = f.Name;
if(xml.Element(fieldName) != null)
{
f.SetValue(target, Convert.ChangeType(xml.Element(fieldName).Value, f.FieldType));
}
}
return target;
}
是以看代碼就很明白了,簡單介紹一下:
- Q:我不知道目标類有啥字段 A:拿到執行個體的Type,之後調用GetFields擷取字段。
- Q:我不知道各個字段是啥類型 A: 其實知道指派目标字段類型的目的就是為了能把從XML中讀取的元素Value類型轉化為字段類型,是以問題就變成了如何把XML的元素Value類型轉化為目标字段類型,是以字段類型為FieldInfo.FieldType,轉化就是Convert.ChangeType(xml.Element(fieldName).Value, f.FieldType)。
- Q:我不知道該如何給字段指派 A:當然還是用反射,FieldInfo.SetValue(obj, obj)。
這樣,一個處理動态讀取XML建立類執行個體并指派的類或者說小工具XMLToEgg就完成了,下面是完整的代碼。
/// <summary>
/// XmlToEgg
/// Created by chenjd
/// http://www.cnblogs.com/murongxiaopifu/
/// https://github.com/chenjd/
/// </summary>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
namespace EggToolkit
{
public static class XmlToEgg<T> where T : class
{
private static string path;
private static T target;
static XmlToEgg()
{
}
/// <summary>
/// Sets the xml path.
/// </summary>
public static void SetXmlPath(string p)
{
path = p;
}
/// <summary>
/// Loads the XML Files.
/// </summary>
private static XElement LoadXML()
{
if(path == null)
return null;
XElement xml = XElement.Load(path);
return xml;
}
/// <summary>
/// Creates the class initiate.
/// </summary>
private static void CreateInitiate()
{
Type t = typeof(T);
ConstructorInfo ct = t.GetConstructor(System.Type.EmptyTypes);
target = (T)ct.Invoke(null);
}
/// <summary>
/// attribute assignment,
/// 由于反射中設定字段值的方法會涉及到指派的目标類型和目前類型的轉化,
/// 是以需要使用Convert.ChangeType進行類型轉化
/// </summary>
public static T ToEgg()
{
if(target != null)
{
target = null;
}
CreateInitiate();
XElement xml = LoadXML();
Type t = target.GetType();
FieldInfo[] fields = t.GetFields();
string fieldName = string.Empty;
foreach(FieldInfo f in fields)
{
fieldName = f.Name;
if(xml.Element(fieldName) != null)
{
f.SetValue(target, Convert.ChangeType(xml.Element(fieldName).Value, f.FieldType));
}
}
return target;
}
}
}
測試:

完整的項目代碼以及使用方法、測試可以從這裡擷取:XMLToEgg(https://github.com/chenjd/Unity3D_XMLToEgg)
裝模作樣的聲明一下:本博文章若非特殊注明皆為原創,若需轉載請保留原文連結(http://www.cnblogs.com/murongxiaopifu/p/4175395.html)及作者資訊慕容小匹夫
更新(之前在遊戲蠻牛更新了,忘了在這裡同步)
有童鞋提出了為什麼不介紹使用序列化和反序列化?小匹夫覺得這個問題挺好哒。那麼就在這裡回答一下:
1序列化&反序列化的應用情景一般是類-->xml-->類有一個儲存的概念在裡面。這裡主要介紹的是純粹從xml到類。如果覺得還是沒差別那麼看下面。
2.聊聊XmlSerializer的實作。
1)XmlSerializer首先你要告訴它你要序列化的類型。例如。XmlSerializer xs = new XmlSerializer(typeof(chenjiadong));
2)XmlSerializer的構造函數會使用 反射 去掃描這個類的内容(用反射并不生成新的代碼)。
3)之後會生成C#的方法去序列化這個類型(此時會生成新的代碼)。
4)并且會動态編譯C#到IL (這樣做當然有好處,就是在序列化和反序列化進行的過程中無需反射,而是直接生成新的代碼去處理,速度上比反射好的多。但是在IOS上新的IL意味着什麼呢?)
5)是以,不管你是序列化,還是反序列化,都會有上面的4個步驟。
3.聊聊這篇文章的目的:細說的含義其實就是講下原理。你可以把文中的XmlToEgg就當成一個類似處理工具,不過本文的目的是介紹XML的讀取,泛型和反射,XmlToEgg是個衍生品。而且其實它的使用也很簡單。
本作品采用知識共享署名-非商業性使用-相同方式共享 2.5 中國大陸許可協定進行許可,我的部落格歡迎複制共享,但在同時,希望保留我的署名權陳嘉棟(慕容小匹夫),并且,不得用于商業用途。如您有任何疑問或者授權方面的協商,請給我留言。
知乎專欄:
Runtime
聯系方式:
Email:[email protected]