使用反射和泛型,动态读取XML创建类实例并赋值

【狗刨学习网】unity极致学院,致力于打造业内unity3d培训、学习第一品牌。

前言:最近小匹夫参与的游戏项目到了需要读取数据的阶段了,那么觉得自己业余时间也该实践下数据相关的内容。那么从哪入手呢?因为用的是Unity3d的游戏引擎,思来想去就选择了C#读取XML文件这个小功能。网上的例子倒也不少,但总是觉得缺点什么。比如读取xml文件之后该如何处理?看到的文章基本上都是手动创建一个目标类的实例,然后手动从读取的XML文件的内容中给刚才创建的目标类实例相关字段赋值。缺点什么呢?对嘞,感觉上不够简单和智能。正所谓驱动科技发展的原因就是懒,为了使我们的小工具能够傻瓜到只需要指定一个需要的目标类型和要读取的xml的地址就能实现目标类实例的动态生成,,下面的文字就诞生了。需要解决的问题:问,从xml文件到需要的目标类实例需要几步?答,读取XML文件,实例化一个目标实例,赋值。问题一:如何读取XML文件所以第一个问题就是如何读取XML文件,参考这篇博客《c#读取XML》,我们可知备选答案无非如下几种:XmlDocumentXmlTextReaderLinq to Xml1.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./// /summarypublic static void SetXmlPath(string p){path = p;}/// summary/// Loads the XML Files./// /summaryprivate static XElement LoadXML(){if(path == null)return null;XElement xml = XElement.Load(path);return xml;}问题二:如何实例化一个目标实例。答案是使用泛型。在实例化具体对象的时候,才确定类型,这样就可以避免由于类型不同而导致的代码无法复用的问题。那么,下面我们的小工具—XMLToEgg就要出场了,对,就是一个处理引用类型的泛型类。public static class XmlToEggT where T : class{}答案是使用反射。那下面继续上代码:/// summary/// Creates the class initiate./// /summaryprivate static void CreateInitiate(){Type t = typeof(T);ConstructorInfo ct = t.GetConstructor(System.Type.EmptyTypes);target = (T)ct.Invoke(null);}当然这里小匹夫假设我们的目标类的构造函数是不需要参数的,如果需要参数也很简单,看官们自己可以查到这里就不赘述了。好了,到这里我们如何创建一个一开始我们不知道是什么类型,只有到创建的时候才知道是什么东西的类的实例的问题就解决了。(好绕)问题三:如何为创建好的实例中的字段赋值好,就算我啥都知道,我应该怎么设呢?直接用instance.field = XXX? 图样图森破。所以问题的本质是明确的:我不知道目标类有啥字段我不知道各个字段是啥类型就算1,2我都知道,但是我就是不知道咋把值赋给相应字段。正所谓"车到山前必有路,答案还是用反射"。只要能解决上面三个小问题,那么最后这一步就算是迈过去了。话不多说,下面上代码:/// summary/// attribute assignment,/// 由于反射中设置字段值的方法会涉及到赋值的目标类型和当前类型的转化,/// 所以需要使用Convert.ChangeType进行类型转化/// /summarypublic 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就完成了,下面是完整的代码。/// /summaryusing 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 XmlToEggT where T : class{private static string path;private static T target;static XmlToEgg(){}/// summary/// Sets the xml path./// /summarypublic static void SetXmlPath(string p){path = p;}/// summary/// Loads the XML Files./// /summaryprivate static XElement LoadXML(){if(path == null)return null;XElement xml = XElement.Load(path);return xml;}/// summary/// Creates the class initiate./// /summaryprivate static void CreateInitiate(){Type t = typeof(T);ConstructorInfo ct = t.GetConstructor(System.Type.EmptyTypes);target = (T)ct.Invoke(null);}/// summary/// attribute assignment,/// 由于反射中设置字段值的方法会涉及到赋值的目标类型和当前类型的转化,/// 所以需要使用Convert.ChangeType进行类型转化/// /summarypublic 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序列化反序列化的应用情景一般是类–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创建类实例并赋值

相关文章:

你感兴趣的文章:

标签云: