前言:

最近小匹夫参与的游戏项目到了需要读取数据的阶段了,那么觉得自己业余时间也该实践下数据相关的内容。那么从哪入手呢?因为用的是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. C#反射实例应用--------获取程序集信息和通过类名创建类实例

    AppDomain.CurrentDomain.GetAssemblies();获取程序集,但是获取的只是已经加载的dll,引用的获取不到. System.Reflection.Assembly.Ge ...

  2. C#反射 获取程序集信息和通过类名创建类实例(转载)

    C#反射获取程序集信息和通过类名创建类实例 . System.Reflection 命名空间:包含通过检查托管代码中程序集.模块.成员.参数和其他实体的元数据来检索其相关信息的类型. Assembly ...

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

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

  4. winform中利用反射实现泛型数据访问对象基类(3)

    继续完善了几点代码 满足没有主键的情况下使用 并且完善实体字段反射设置value时的类型转换 /// <summary> /// DAO基类 实体名必须要与数据表字段名一致 /// < ...

  5. winform中利用反射实现泛型数据访问对象基类(1)

    考虑到软件使用在客户端,同时想简化代码的实现,就写了一个泛型的数据访问对象基类,并不是特别健全,按道理应该参数化的方式实现insert和update,暂未使用参数化,抽时间改进. /// <su ...

  6. java反射中的动态代理机制(有实例)

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

  7. winform中利用反射实现泛型数据访问对象基类(2)

    在1的基础上做了一点改进 参数化处理 看上去更简洁 无主键情况下 update 方法需要改进 insert delete没有问题  /// <summary>     /// DAO基类 ...

  8. 关于Emit中动态类型TypeBuilder创建类标记的一点思考

      利用TypeBuilder是可以动态创建一个类型,现在有个需求,动态生成一个dll,创建类型EmployeeEx,需要继承原dll里面的Employee类,并包含Employee类上的所有类标记. ...

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

    子程序定义: public delegate void CurrentControlListenEvent(string uniqueID, string way = null); public ev ...

随机推荐

  1. FFmpeg学习6:视音频同步

    在上一篇文章中,视频和音频是各自独立播放的,并不同步.本文主要描述了如何以音频的播放时长为基准,将视频同步到音频上以实现视音频的同步播放的.主要有以下几个方面的内容 视音频同步的简单介绍 DTS 和 ...

  2. SSH实战 · 唯唯乐购项目(上)

    前台需求分析 一:用户模块 注册 前台JS校验 使用AJAX完成对用户名(邮箱)的异步校验 后台Struts2校验 验证码 发送激活邮件 将用户信息存入到数据库 激活 点击激活邮件中的链接完成激活 根 ...

  3. DB1:数据库的创建和文件的修改

    在SQL Server中,使用Create Database创建数据库,使用Alter Database命令,能够修改数据库的数据文件和日志文件. 一,创建数据库 1,在创建数据库时,最佳实践是: 创 ...

  4. Android学习路线总结,绝对干货

    title: Android学习路线总结,绝对干货 tags: Android学习路线,Android学习资料,怎么学习android grammar_cjkRuby: true --- 一.前言 不 ...

  5. TypeScript为Zepto编写LazyLoad插件

    平时项目中使用的全部是jQuery框架,但是对于做webapp来说jQuery太过于庞大,当然你可以选择jQuery 2.*针对移动端的版本. 这里我采用移动端使用率比较多的zepto框架,他跟jqu ...

  6. 一个软件开发者的BPM之路

    我是小林,一名普通的软件工程师,从事BPM(业务流程管理)软件开发工作.我没有几十年的技术底蕴,无法像大牛们一样高谈阔论,品评BPM开发之道:也不是资深的流程管理专家,能与大家分析流程管理的时弊.我只 ...

  7. Android菜单项内容大全

    一.介绍: 菜单是许多应用中常见的用户界面组件. Android3.0版本以前,Android设备会提供一个专用"菜单"按钮呈现常用的一些用户操作, Android3.0版本以后, ...

  8. JavaScript 写计算器改进版

    <html><head><style> .short{height:50px;width:55px;float:left;}</style></h ...

  9. Linux.NET实战手记—自己动手改泥鳅(上)

    各位读者大家好,不知各位读者有否阅读在下的前一个系列<Linux.NET 学习手记>,在前一个系列中,我们从Linux中Mono的编译安装开始,到Jexus服务器的介绍,以及如何在Linu ...

  10. C#执行异步操作的几种方式比较和总结

    C#执行异步操作的几种方式比较和总结 0x00 引言 之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理.对XxxxxAsync()之类的方法也没去了解过, ...