前言:

最近小匹夫参与的游戏项目到了需要读取数据的阶段了,那么觉得自己业余时间也该实践下数据相关的内容。那么从哪入手呢?因为用的是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. Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)

    本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...

  2. C语言 · Anagrams问题

    问题描述 Anagrams指的是具有如下特性的两个单词:在这两个单词当中,每一个英文字母(不区分大小写)所出现的次数都是相同的.例如,"Unclear"和"Nuclear ...

  3. C语言 · 数位分离

    问题描述 编写一个程序,输入一个1000 以内的正整数,然后把这个整数的每一位数字都分离出来,并逐一地显示. 输入格式:输入只有一行,即一个1000以内的正整数. 输出格式:输出只有一行,即该整数的每 ...

  4. CodeSimth - .Net Framework Data Provider 可能没有安装。解决方法

    今天想使用CodeSimth生成一个sqlite数据库的模板.当添加添加数据库的时候发现: .Net Framework Data Provider 可能没有安装. 下面找到官方的文档说明: SQLi ...

  5. MVC Core 网站开发(Ninesky) 2.1、栏目的前台显示

    上次创建了栏目模型,这次主要做栏目的前台显示.涉及到数据存储层.业务逻辑层和Web层.用到了迁移,更新数据库和注入的一些内容. 一.添加数据存储层 1.添加Ninesky.DataLibrary(与上 ...

  6. C#异步编程(二)

    async和await结构 序 前篇博客异步编程系列(一) 已经介绍了何谓异步编程,这篇主要介绍怎么实现异步编程,主要通过C#5.0引入的async/await来实现. BeginInvoke和End ...

  7. var和dynamic的区别

    1.var 1.均是声明动态类型的变量. 2.在编译阶段已经确定类型,在初始化的时候必须提供初始化的值. 3.无法作为方法参数类型,也无法作为返回值类型. 2.dynamic 1.均是声明动态类型的变 ...

  8. log4net使用手册

    1. log4net简介 log4net是.Net下一个非常优秀的开源日志记录组件.log4net记录日志的功能非常强大.它可以将日志分不同的等级,以不同的格式,输出到不同的媒介.Java平台下,它还 ...

  9. 分页插件--根据Bootstrap Paginator改写的js插件

    刚刚出来实习,之前实习的公司有一个分页插件,和后端的数据字典约定好了的,基本上是看不到内部是怎么实现的,新公司是做WPF的,好像对于ASP.NET的东西不多,导师扔了一个小系统给我和另一个同事,指了两 ...

  10. 嵌入式&iOS:回调函数(C)与block(OC)传 参/函数 对比

    C的回调函数: callBack.h 1).声明一个doSomeThingCount函数,参数为一个(无返回值,1个int参数的)函数. void DSTCount(void(*CallBack)(i ...