本身動手之使用反射和泛型,動態讀取XML建立類實例並賦值

前言:

最近小匹夫參與的遊戲項目到了須要讀取數據的階段了,那麼以爲本身業餘時間也該實踐下數據相關的內容。那麼從哪入手呢?由於用的是Unity3d的遊戲引擎,思來想去就選擇了C#讀取XML文件這個小功能。網上的例子倒也很多,但老是以爲缺點什麼。好比讀取xml文件以後該如何處理?看到的文章基本上都是手動建立一個目標類的實例,而後手動從讀取的XML文件的內容中給剛纔建立的目標類實例相關字段賦值。缺點什麼呢?對嘞,感受上不夠簡單和智能。html

正所謂驅動科技發展的緣由就是懶,爲了使咱們的小工具可以傻瓜到只須要指定一個須要的目標類型和要讀取的xml的地址就能實現目標類實例的動態生成,下面的文字就誕生了。git

須要解決的問題:

問,從xml文件到須要的目標類實例須要幾步?github

答,讀取XML文件,實例化一個目標實例,賦值。c#

問題一:如何讀取XML文件

因此第一個問題就是如何讀取XML文件,參考這篇博客《c#讀取XML》,咱們可知備選答案無非以下幾種:函數

  1. XmlDocument
  2. XmlTextReader
  3. 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的使用:spa

//XmlTestReader的使用方法 
XmlTextReader reader = new XmlTextReader("./Assets/xml-to-egg/xml-to-egg-test/Test.xml");
//使用read()方法向下讀取
while (reader.Read())
{
  .....  
}

要說明與XmlDocument的最大區別,其實也很簡單,XmlReader使用Steam(流)來讀取文件,因此不會對內存形成太大的消耗。XmlReader經過read()方法不斷向下讀取,咱們就能夠在這個過程當中進行咱們須要的操做。不過這個也不是咱們的答案,咱們選擇的答案在下面。3d

3.Linq to Xmlcode

在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. 我不知道各個字段是啥類型
  3. 就算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;
}

因此看代碼就很明白了,簡單介紹一下:

  1. Q:我不知道目標類有啥字段 A:拿到實例的Type,以後調用GetFields獲取字段。
  2. Q:我不知道各個字段是啥類型 A: 其實知道賦值目標字段類型的目的就是爲了能把從XML中讀取的元素Value類型轉化爲字段類型,因此問題就變成了如何把XML的元素Value類型轉化爲目標字段類型,因此字段類型爲FieldInfo.FieldType,轉化就是Convert.ChangeType(xml.Element(fieldName).Value, f.FieldType)。
  3. 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是個衍生品。並且其實它的使用也很簡單。

相關文章
相關標籤/搜索