前言:

最近小匹夫参与的游戏项目到了需要读取数据的阶段了,那么觉得自己业余时间也该实践下数据相关的内容。那么从哪入手呢?因为用的是Unity3d的游戏引擎,思来想去就选择了C#读取XML文件这个小功能。网上的例子倒也不少,但总是觉得缺点什么。比如读取xml文件之后该如何处理?看到的文章基本上都是手动创建一个目标类的实例,然后手动从读取的XML文件的内容中给刚才创建的目标类实例相关字段赋值。缺点什么呢?对嘞,感觉上不够简单和智能。

正所谓驱动科技发展的原因就是懒,为了使我们的小工具能够傻瓜到只需要指定一个需要的目标类型和要读取的xml的地址就能实现目标类实例的动态生成,下面的文字就诞生了。

需要解决的问题:

问,从xml文件到需要的目标类实例需要几步?

答,读取XML文件,实例化一个目标实例,赋值。

问题一:如何读取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的使用:

//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. 我不知道各个字段是啥类型
  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是个衍生品。而且其实它的使用也很简单。

自己动手之使用反射和泛型,动态读取XML创建类实例并赋值相关推荐

  1. python创建类的实例方法-Python中动态创建类实例的方法

    简介 在Java中我们可以通过反射来根据类名创建类实例,那么在Python我们怎么实现类似功能呢? 其实在Python有一个builtin函数import,我们可以使用这个函数来在运行时动态加载一些模 ...

  2. C# 反射 通过类名创建类实例

    "反射"其实就是利用程序集的元数据信息. 反射可以有很多方法,编写程序时请先导入 System.Reflection 命名空间. 1.假设你要反射一个 DLL 中的类,并且没有引用 ...

  3. 利用反射获得委托和事件以及创建委托实例和添加事件处理程序

    最近一些都在看关于反射的内容,然后在网上大多数都是通过反射获得类型中方法,属性.字段这样的文章, 但是对于如何获得委托类型怎么去实现的却没有, 所以写下这边篇文章来让自己以后很好的复习以及想了解的朋友 ...

  4. C#反射技术之一动态读取和设置对象的属性值

    要用C#反射技术的话,首先得引入System.Reflection 命名空间,这个命名空间里的类,具有动态加载程序集.类型,动态调用方法.设置和取得属性和字段的值.可以获取类型和方法的信息的功能. 要 ...

  5. 面试题,反射创建类实例的三种方式是什么

    1.获得Class:主要有三种方法: (1)Object-->getClass (2)任何数据类型(包括基本的数据类型)都有一个"静态"的class属性 (3)通过class ...

  6. 牛逼!java反射创建类实例的三种方式

    说到博客,大家会不由自主的地想到博客园,CSDN,GitHub,简书等.在这些平台里,有着无数的程序员发表自己的博客,这些博客有的是纯技术干货的分享,有的是对编程思想的感悟.可以说,一个好的博客平台是 ...

  7. JS通过ajax动态读取xml文件内容

    http://www.sharejs.com/codes/javascript/8178 HTML文件代码如下 <!DOCTYPE html> <html> <head& ...

  8. 慕容小匹夫 Unity3D移动平台动态读取外部文件全解析

    Unity3D移动平台动态读取外部文件全解析 c#语言规范 阅读目录 前言: 假如我想在editor里动态读取文件 移动平台的资源路径问题 移动平台读取外部文件的方法 补充: 回到目录 前言: 一直有 ...

  9. 匹夫细说Unity3D(一)——移动平台动态读取外部文件全解析

    前言: 一直有个想法,就是把工作中遇到的坑通过自己的深挖总结成一套相同 问题 的解决方案供各位同行拍砖探讨.眼瞅着2015年第一个工作日就要来到了,小匹夫也休息的差不多了,寻思着也该写点东西活动活动大 ...

最新文章

  1. 看闯关东原来知道古代已经十六进制了
  2. html 相对于父标签位置,css子元素如何相对父元素定位?
  3. CTFshow php特性 web144
  4. 无线网络连接无法停用
  5. wxWidgets:wxSplitterWindow概述
  6. DLL内线程同步主线程研究(子线程代码放到主线程执行)
  7. QQ协议调试器 QQDebugger
  8. python property setter_Python:动态属性 property setter 以及 __getattr__ 属性
  9. SQL(二)- 基础查询语句
  10. Oracle入门(十四.21)之创建DML触发器:第二部分
  11. css基础选择器 1204
  12. 【英语学习】【Daily English】U01 Greetings / L02 What brings you here?
  13. java如何画矩形条和填充_java.awt.Graphics 类的哪个方法可绘制填充矩形?
  14. 计算机用户在使用计算机文件时6,201606-计算机基础选择题(含答案)(6页)-原创力文档...
  15. redis教程(七)之redis List
  16. 精美绝伦的KShong GHOST Windows7-Pro 2010幸福版
  17. 一笔画: 表现绘画过程的美
  18. linux下安装sqlite3
  19. C# 实现Windows Media Encoder音视频捕捉
  20. 服务器文件夹共享到本地,云服务器共享本地文件夹

热门文章

  1. debian文本配置网络备忘:/etc/network/interfaces
  2. Sublime Text2,跨平台神级编辑器乱码问题解决
  3. 33 个送给 Java 程序员的练手项目合集
  4. synchronized概念
  5. Docker Client(Docker 客户端)
  6. MyBatis 源码解读-XMLConfigBuilder
  7. 装饰者模式在源码中的应用
  8. Redis中的Sentinel 验证
  9. 区域数据导入功能(POI使用方式)
  10. 资源权限操作-查询所有资源权限