文章目录
行文背景
近期在学习一个AB包生成和加载框架,里面有一部分关于配置表的课程。于是把这部分单独拿出来,并且进行了一定的修改,以巩固学习到的知识。这里记录一下框架中用到的知识(相关类以及用法)。C#中的反射
1.基本释义
反射提供描述程序集、模块和类型的对象(Type 类型)。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性。 如果代码中使用了特性,可以利用反射来访问它们。 通俗的来讲,我们这里用到的是动态创建一个已有或者未知的类,然后给它的成员进行赋值,或者运行其中的方法成员。以至于我们能够根据数据来生成所想要的类,而不用提前知道这个类是什么。 程序集:System.Reflection.Emit,System.Reflection2.动态创建类
通常的流程图:
代码示例如下:
public static void CreateType(string className, ref Type classType, ref Type enumType)
{
//动态创建程序集
AssemblyName demoName = new AssemblyName("MyTestExcel");
AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(demoName, AssemblyBuilderAccess.Run);
//动态创建模块
ModuleBuilder mb = dynamicAssembly.DefineDynamicModule("MyTestModule");
//在模块下动态创建类
TypeBuilder tb = mb.DefineType(className, TypeAttributes.Public);
//在类下定义一个带整型参数的构造函数将其存储在私有字段中。
Type[] parameterTypes = { typeof(int) };
ConstructorBuilder ctor1 = tb.DefineConstructor(MethodAttributes.Public,CallingConventions.Standard,parameterTypes);
//在类下定义一个叫Property字段的字符串数组(属性)
PropertyBuilder pbString = tb.DefineProperty("Property字段", PropertyAttributes.HasDefault | PropertyAttributes.None, typeof(string[]),null);
//在类下定义一个叫Field字段的静态的公有的字符串数组(字段)
FieldBuilder fbNumber = tb.DefineField("Field字段", typeof(string), FieldAttributes.Public|FieldAttributes.Static);
在类下定义一个方法(看起来很难,要学MISL中间语言)
//MethodBuilder mbBuilder = tb.DefineMethod("Method方法", MethodAttributes.Public | MethodAttributes.Static);
//ILGenerator mbIL = mbBuilder.GetILGenerator();
//mbIL.Emit(OpCodes.Ldarg_0);
//mbIL.Emit(OpCodes.Call);
//在模块下动态创建一个枚举类
EnumBuilder eb = mb.DefineEnum("Elevation", TypeAttributes.Public, typeof(int));
eb.DefineLiteral("Low", 0);
eb.DefineLiteral("High", 1);
enumType = eb.CreateTypeInfo();
classType = tb.CreateType();
}
定义构造函数和方法,使用ILGenerator类进行方法体的创建,这里暂时没看懂/(ㄒoㄒ)/~~。看了下,可能需要研修下MISL是如何运行的。。。方法和字段的特性(私有,公有等)则选择相应的枚举类型进行定义。
3.实例化类并获取成员
Activator,首先使用的是这个类,在System命名空间下,用以在本地或从远程创建对象类型,或获取对现有远程对象的引用。通常我们使用的方法是CreateInstance(Type),使用类型的无参数构造函数创建指定类型的实例。如果有其他需求,可以查看它的重载方法,很多并且够用! 而获取实例化后的成员则是使用Type类下的各种获取成员的方法:GetEnumNames() :返回当前枚举类型中各个成员的名称
GetEvent(String) :返回表示指定的公共事件的 EventInfo 对象
GetEvents() :返回由当前 Type 声明或继承的所有公共事件
GetField(String) :搜索具有指定名称的公共字段
GetFields() :返回当前 Type 的所有公共字段
GetMember(String) :搜索具有指定名称的公共成员
GetMembers() :返回为当前 Type 的所有公共成员
GetMethod(String) :搜索具有指定名称的公共方法
GetMethods() :返回为当前 Type 的所有公共方法
GetProperty(String) :搜索具有指定名称的公共属性
GetProperties() :返回为当前 Type 的所有公共属性
GetConstructors() :返回为当前 Type 定义的所有公共构造函数
GetEnumName(Object) :返回当前枚举类型中具有指定值的常数的名称
GetConstructor(Type[]) :搜索其参数与指定数组中的类型匹配的公共实例构造函数
string[] fieldName = new string[] { "11", "22", "33" };
Type type = null;
Type type1 = null;
CreateType("TestClass", ref type, ref type1);
object obj = Activator.CreateInstance(type);
FieldInfo[] infoes = obj.GetType().GetFields();
foreach(var info in infoes)
{
if (info.FieldType == typeof(string[]))
{
info.SetValue(obj, fieldName);
}
}
object str = obj.GetType().GetField("Field字段").GetValue(obj);
str = " 我是一个string字段 ";
只是举个简单的例子,有一个比较需要注意的是对于集合类的字段或者属性,使用SetValue的方法进行赋值是比较安全的,而其他的则可以使用直接赋值。例如上诉代码中的str。SetValue在各种Get方法的返回值类中都是带有的。
读写文档中的数据
1.Xml文档
程序的语言中,读取文档数据生成类,叫做反序列化,而反过来叫序列化。在C#中有对应的类来承担xml文档序列化的功能,所以我们很简单就能写出对应的代码。在System.Xml.Serialization程序集下的XmlSerializer类来承担序列化和反序列化的功能。Deserialize(Stream):反序列化指定 Stream 包含的 XML 文档
Serialize(Stream, Object):使用指定的 Object 序列化指定的 Stream 并将 XML 文档写入文件
/// <summary>
/// 类序列化成xml
/// </summary>
public static bool XmlSerialize(string path, object obj)
{
try
{
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
{
XmlSerializer xs = new XmlSerializer(obj.GetType());
xs.Serialize(sw, obj);
}
}
return true;
}
catch (Exception e)
{
Debug.LogError("此类无法转换成xml: " + obj.GetType() + "," + e);
}
return false;
}
/// <summary>
/// 反序列化xml文件
/// </summary>
public static T XmlDeserialize<T>(string path) where T : class
{
T t = default(T);
try
{
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
XmlSerializer xs = new XmlSerializer(typeof(T));
t = (T)xs.Deserialize(fs);
}
return t;
}
catch (Exception e)
{
Debug.LogError("Xml文件读取错误:" + path + "," + e);
}
return t;
}
2.Binay文档
同上Xml一样,二进制也有相应的类来实现序列化和反序列化的功能,在程序集System.Runtime.Serialization.Formatters.Binary下。代码则和xml类似,把中间的类XmlSerializer换成二进制的BinaryFormatter即可,如下:/// <summary>
/// 类序列化成二进制
/// </summary>
public static bool BinarySerialize(string path, object obj)
{
try
{
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
BinaryFormatter xs = new BinaryFormatter();
xs.Serialize(fs, obj);
}
return true;
}
catch (Exception e)
{
Debug.LogError("此类无法转换成二进制: " + obj.GetType() + "," + e);
}
return false;
}
/// <summary>
/// 反序列化二进制
/// </summary>
public static object BinaryDeserialize(string path)
{
object obj = null;
try
{
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
BinaryFormatter xs = new BinaryFormatter();
obj = xs.Deserialize(fs);
}
}
catch(Exception e)
{
Debug.LogError("二进制文件读取错误: " + path + "," + e);
}
return obj;
}
3.Excel文档
对于excel文档,则有很多种选择,出去系统自带的方法,各种眼花缭乱的插件也是数不胜数。这里使用了一个叫Epplus的开源插件,具体的使用方法就不介绍了,主要使用的是它的ExcelPackage类,ExcelWorkSheets类。获取到文档内容之后进行遍历赋值。在读取数据的时候,提前定义SheetWork和SheetData数据类,方便转换类的数据到Excel的形式。/// <summary>
/// Excel标签页数据类
/// </summary>
public class SheetWork
{
public List<SheetData> allSheetData = new List<SheetData>();
}
/// <summary>
/// Excel表格数据类
/// </summary>
public class SheetData
{
public string Name { get; set; }
public Type Type { get; set; }
public string Data { get; set; }
}
Excel,Xml和Binary互相转换
1,转换流程
一张简单的图,展示下互相转换是如何实现的:2,一些小知识
不管如何转换,都是使用类做中间环节。对于已知的类可以直接实例化进行数据读取和转换,而对于不存在的类,则需要使用第一节介绍的动态创建进行生成相应的类。几个简单的知识点在下面附上代码。 如何在程序集中搜索类:Type type = null;
//AppDomain,应用程序域,GetAssemblies方法可以获取当前程序域中的程序集(Assembly数组)
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
type = asm.GetType(name);
if (type != null) break;
}
获取unity中选中的文件的名字:
[MenuItem("Assets / Excel和Xml / Class转Binary")]
public static void AssetsClassToBinary()
{
//选中的文件Object数组
UnityEngine.Object[] objs = Selection.objects;
for (int i = 0; i < objs.Length; i++)
{
//显示进度条
EditorUtility.DisplayProgressBar("文件下的Class转Binary", "正在扫描" + objs[i].name + "... ...", 1.0f / objs.Length * i);
ClassToBinary(objs[i].name);
//Debug.Log("Class转Binary已完成:" + objs[i].name);
Debug.Log(objs[i].name + "Class转Binary已完成,路径为:" + dataPath + objs[i].name + ".byte");
}
//刷新资源管理器
AssetDatabase.Refresh();
//关闭进度条
EditorUtility.ClearProgressBar();
}