1 什么是程序集?
程序集是经由编译器编译得到的,供进一步编译执行的那个中间产物,在WINDOWS系统中,它一般表现为后缀为·dll(库文件)或者是·exe(可执行文件)的格式
程序集就是我们写的一个代码集合,我们现在写的所有代码最终都会被编译器翻译为一个程序集供别人使用,比如一个代码库文件(dll)或者一个可执行文件(exe)。
2 什么是元数据?
元数据(又称中介数据,中继数据)就是用来描述数据的数据,描述信息资源或数据等对象的数据。这个概念不仅仅用于程序上,在别的领域也有元数据。主要是描述数据属性的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。可以在不能完整的了解对象时,用使用对象。
程序的元数据是什么?程序中的类,类中的函数、变量等等信息,它们保存在程序集中。
在.Net框架中,把元数据类比成属性。就很容易理解了。
3 什么是反射?
3.1 反射的概念
一个运行的程序查看本身或者其它程序的元数据的行为就叫做反射。不理解没关系,我们换种说法,程序正在运行时,可以查看其它程序集或者自身的属性就叫反射。比如得到类,函数,变量,对象等等,实例化它们,执行它们,操作它们。
3.2 反射的作用
因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。方便在运行时(不能直接实例化),调用程序集中的元数据,进行操作。具体作用如下:
1.程序运行时,得到所有元数据,包括元数据的特性
2.程序运行时,实例化对象,操作对象
3.3 语法相关
首先我们定义一个类并把它当成是运行时候我们需要获取数据的程序集。
public class Test
{
public Test(){}
public Test(int i){}
public Test(int i,string str) : this(i)
{
this.str = str;
}
}
3.3.1 Type(类的信息)
Type是反射功能的基础,访问元数据的主要方式。使用 Type 的成员可以获取有关类型声明的信息,有关类型的成员(如构造函数、方法、字段、属性和类的事件)。
那么我们怎么获取Type呢?主要有三种方式。
Type type = | |
---|---|
利用万物object中的GetType() | object.GetType() |
利用关键字typeof,传入类名 | typeof(类名) |
利用类的名字下的GetType(),传入命名空间.类名 | 类的名字.GetType("命名空间.类名") |
备注 | 三者获取的type在堆里的地址是一样的 |
3.3.1.1 获取类中的所有公共成员
这里我们学到了装载成员信息的MemberInfo[]
,和获取成员的函数方法t.GetMembers()
。
//需要引用命名空间 using System.Reflection;
//1.得到Type
Type t = typeof(Test);
//2.得到所有公共成员
MemberInfo[] infos = t.GetMembers();
3.3.1.2 获取类的公共构造函数并调用
1.获取所有构造函数
这里我们学到了装载构造函数的ConstructorInfo[]
,和获取构造函数方法t.GetConstructors()
。
//1.获取所有构造函数
ConstructorInfo[] ctors = t.GetConstructors();
2.获取其中一个构造函数并执行
得到构造函数时,传入Type数组new Type[]{}
中内容是该构造函数按顺序的参数类型typeof(int)
;执行info.Invoke()
构造函数传入 object数组new object[]{}
中填写按顺序传入的具体参数。
//传入的数组长度是0,代表无参构造
ConstructorInfo info = t.GetConstructor(new Type[0]);
//执行无参构造 无参构造 没有参数 传null
Test obj = info.Invoke(null) as Test;
//一个参数的
//数组的第一个参数类型是(int)
ConstructorInfo info2 = t.GetConstructor(new Type[] { typeof(int) });
//用test类型的obj来装载,执行 有参构造函数
obj = info2.Invoke(new object[] { 2 }) as Test;
//两个参数的
ConstructorInfo info3 = t.GetConstructor(new Type[] { typeof(int), typeof(string)});
obj = info3.Invoke(new object[] { 4, "444444" }) as Test;
3.3.1.3 获取和设置公共成员变量
首先学习到了装载成员变量的容器FieldInfo
,通过FieldInfo[]
装载多个,然后学习到了通过类型获取变量的方法t.GetField()
,也可以传入变量名来得到指定参数t.GetField("j")
。
如果要获取和设置对象的某个变量值,infoJ.GetValue(test)
,infoJ.SetValue(test,value)
。
//1.得到所有成员变量
FieldInfo[] fieldInfos = t.GetFields();
//2.得到指定名称的公共成员变量
FieldInfo infoJ = t.GetField("j");
//3.通过反射获取和设置对象的值
Test test = new Test();
test.j = 99;
test.str = "2222";
// 3-1通过反射 获取对象的某个变量的值
Console.WriteLine(infoJ.GetValue(test));
// 3-2通过反射 设置指定对象的某个变量的值
infoJ.SetValue(test, 100);
Console.WriteLine(infoJ.GetValue(test));
3.3.1.4 获取和执行公共成员方法
装载方法的容器MethodInfo
,获取方法的函数strType.GetMethods()
, 如果存在方法重载 用Type数组(https://www.yuque.com/tianstudio/nqufed/gtkuvu#App1i)得到相应的方法,strType.GetMethod("Substring",new Type[] { typeof(int), typeof(int) })
。
执行的方式也和构造函数类似,通过获取的方法subStr
来使用Invoke激活但是多了一个对象,所以就有两个参数,第一个参数 相当于 是哪个对象要执行这个成员方法,第二个参数就是执行方法要传入的参数,用object数组来传参数subStr.Invoke(str, new object[] { 7, 5 })
,返回的参数是object类型。
//通过Type类中的 GetMethod方法 得到类中的方法
//MethodInfo 是方法的反射信息
Type strType = typeof(string);
MethodInfo[] methods = strType.GetMethods();
//1.如果存在方法重载 用Type数组表示参数类型
MethodInfo subStr = strType.GetMethod("Substring",
new Type[] { typeof(int), typeof(int) });
//2.调用该方法
//注意:如果是静态方法 Invoke中的第一个参数传null即可
string str = "Hello,World!";
//第一个参数 相当于 是哪个对象要执行这个成员方法
//第二个参数就是 传入的参数
object result = subStr.Invoke(str, new object[] { 7, 5 });
3.3.1.5 Type中的其他方法
得枚举GetEnumName``GetEnumNames
,得事件GetEvent``GetEvents
,得接口GetInterface``GetInterfaces
,得属性GetProperty ``GetPropertys
。
总的来说就是各种Get,然后加上名字。
3.3.2 Activator 激活器类——用于快速实例化
先得到了一个Type型t
,然后使用Activator.CreateInstance()
方法;区别是,如果是初始化是无参构造,则参数传入类型t
,如果是有参构造,则参数依次是类型t``具体的参数
,返回值为object。
//先得到Type
//然后 快速实例化一个对象
Type testType = typeof(Test);
//1.无参构造
Test testObj = Activator.CreateInstance(testType) as Test;
//2.有参数构造
//这里是实例化的同时,初始化构造函数
testObj = Activator.CreateInstance(testType, 99) as Test;
testObj = Activator.CreateInstance(testType, 55, "111222") as Test;
3.3.3 Assembly程序集类
程序集类主要用来加载其他程序集,加载后才能用Type来使用其他的程序集,比如dll文件(库文件——提供一些可以直接使用的变量、函数和类)。
首先学习到装载程序集的容器Assembly
,加载使用Assembly下的函数方法,同一文件下加载
**Load**("程序集名称")
,不同文件下加载**LoadFrom**("包含程序集清单的文件的名称或路径")
**LoadFile**("要加载的文件的完全限定路径");
//一般用来加载在同一文件下的其它程序集
Assembly asembly2 = Assembly.Load("程序集名称");
//一般用来加载不在同一文件下的其它程序集
Assembly asembly = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
Assembly asembly3 = Assembly.LoadFile("要加载的文件的完全限定路径");
举例说明:
这里的asembly就是一个程序集,通过使用程序集的名字的方法获取它的类型,才能使用反射。
//1.先加载一个指定程序集
Assembly asembly = Assembly.LoadFrom(@"C:\Users\MECHREVO\Desktop\CSharp进阶教学\Lesson18_练习题\bin\Debug\netcoreapp3.1\Lesson18_练习题");
//2.再 加载程序集中 的 一个类对象 之后才能使用反射
Type icon = asembly.GetType("Lesson18_练习题.Icon");//完整的命名空间和类名
//用万物之父object装载实例:
object iconObj = Activator.CreateInstance(icon, 10, 5, right.GetValue(null));
//首先得到枚举Type 来得到可以传入的参数
Type moveDir = asembly.GetType("Lesson18_练习题.E_MoveDir");
//得到成员变量
FieldInfo right = moveDir.GetField("Right");
3.4 总结
反射精华:通过类型,获取变量,获取方法;再通过变量,获取值和修改值,通过方法点出Invoke执行。