目录
什么是反射?
为什么使用反射?
反射机制的优缺点
如何使用反射?
一,Type访问元数据
获取/修改类中公有成员(属性PropertyI和字段Field等)
调用类中的公有构造函数Constructor
调用类中的公有方法Method
获取类中的私有成员(包括方法)
二,Activator快速实例化对象
三,Assembly加载程序集
反射是.NET Framework 的功能,并不是C#语言特有的功能。简单理解来看就是:
给我一个对象,我能在不用new操作符的情况下,也不知道是什么静态类型的情况下,创建一个同类型的对象,同时还能访问这个对象的各个成员。(在获取其他实体类的字段名或实列,只能获取公有的,而有了反射之后可以获取私有的,可以获取他的基类等等,可以说把家底查得清清楚楚。)
很多时候程序的逻辑并不是在写的时候就能确定,有时需要用户交互时才确定,但此时程序已经运行。如果要程序员在静态编写时,去枚举用户可能做的操作,会让程序变得十分臃肿,可读性、可维护性都很烂,并且枚举用户可能做的操作这件事是很难实现的。这是我们需要的这种:以不变应万变的能力,就是反射机制。
通过反射在运行时取得一些编译期不存在的类型的信息,创建实例,或者调用方法。
优点
缺点
通过反射可以动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性,反射的语法主要通过三个类来实现:分别是Type、Assembly和Activator。
首先创建一个测试类Students方便测试:
class Students
{
public string Name { get; set; }
public int Age { get; set; }
//公有字段
public int a = 0;
//私有属性
private int weight { get; set; }
public Students(string name,int age)
{
this.Name = name;
this.Age = age;
}
public void message()
{
Console.WriteLine($"同学:{this.Name}已注册");
}
}
Type是类的信息类,它是反射功能的挤出,访问元数据的主要方式。
使用Type的成员获取有关类型(如构造函数、方法、字段、属性和类的事件等)声明的信息,具体步骤如下:
(1)获取Type,得到Students类的程序集信息,获取Type对象有两种方法:
//第一种:类未实例化(没用对象)
Type type = typeof(Students);
//第二种:类已实例化(new构建对象)
Students s= new Students();
Type studentsType = s.GetType();
(2)获取Students类中的所有的信息
首先变量类型声明方式创建一个 Type 类型的变量 type,表示 Students类型。接着,使用反射 API 获取 Student 类型的所有公共成员(属性和字段(变量))等信息,使用 foreach 遍历每个成员信息对象。
由于属性信息(PropertyInfo)和字段信息(FieldInfo)的基类都是 MemberInfo,因此可以通过 GetValue(Object obj)和SetValue(Object obj,Object value)对单个公有成员进行获取/设置。
Students student = new Students("李华",23);
Type type = student.GetType();
//获取属性
{
//方式一(获取类中全部属性)
PropertyInfo[] studentType = type.GetProperties();
//方式二(获取类中指定属性)
var property = type.GetProperty("Age");
foreach (var stu in studentType)
{
//获取属性值
object obj = stu.GetValue(student);
Console.WriteLine($"{obj}");
}
//修改Age属性值为50
property.SetValue(student, 50);
Console.WriteLine($"修改后Age的值:{property.GetValue(student)}");
}
//获取字段(修改字段值和上面写法一致)
{
FieldInfo[] fieldInfo = type.GetFields();
//获取变量a的类型
var filed = type.GetField("a");
}
获取其他公有成员变量大同小异,这里只提示方法并不一一赘述。
1、得到枚举:GetEnumName、GetEnumNames
2、得到事件:GetEvent、GetEvents
3、得到接口:GetInterface、GetInterfaces
获取类中的公有构造函数可以通过GetConstructor和GetConstructors获得,通过ConstructorInfo接收(或使用var类型),通过Invoke函数实例化构造函数。最后通过实例化对象可间接调用对象的方法以及对象的成员信息。
//获取构造函数
{
//(一)获取无参构造函数
ConstructorInfo info = type.GetConstructor(new Type[0]);
//执行无参构造 无参构造没有参数传null
Students obj = info.Invoke(null) as Students;
//调用对象的方法
obj.message();
//(二)获取有参数构造函数
ConstructorInfo info2 = type.GetConstructor(new Type[] { typeof(string), typeof(int) });
//执行有参构造函数
obj = info2.Invoke(new object[] { "小红", 11 }) as Students;
obj.message();
}
获取成员方法可以通过Type类中的GetMethod方法来得到类中的方法,MethodInfo是方法的反射信息。最后通过Invoke函数进行调用,如果存在方法重载,用Type数组表示参数类型。
需要注意:当调用的是静态方法时,Invoke函数的第一个参数为null。
var method = type.GetMethods();
foreach (var item in method)
{
//Invoke(object obj,object value)
item.Invoke(student,null);
}
//打印结果:
//同学:李华已注册
反射最牛的地方就是它可以获取私有方法与实列:
(1)BindingFlags.Instance 表示只获取实例方法
(2)BindingFlags.NonPublic 表示只获取非公共方法
(3)BindingFlags.Public 表示只获取公共方法
将测试类Students的成员变量修改为私有属性(private),再通过反射获取类中的私有成员时,需要设置获取成员API的BindingFlags参数,以上文获取类中属性为例:
Students student = new Students("李华",23);
Type type = student.GetType();
//获取属性
{
//方式一(获取类中全部属性)
PropertyInfo[] studentType = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic);
//方式二(获取类中指定属性)
var property = type.GetProperty("Age",BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var stu in studentType)
{
//获取属性值
object obj = stu.GetValue(student);
Console.WriteLine($"{obj}");
}
//修改Age属性值为50
property.SetValue(student, 50);
Console.WriteLine($"修改后Age的值:{property.GetValue(student)}");
}
Activator用于实例化对象的类,通过type获取类型后,可以通过它实例化对象。Activator.CreateInstance默认调用无参构造函数。
1、无参构造函数:
Type stu= typeof(Students);
//快速实例化Students对象testObj
Test testObj = Activator.CreateInstance(stu) as Students;
2、有参构造函数:
如果要调用有参构造函数,在后面一次添加参数即可。
testObj = Activator.CreateInstance("小黄", 29) as Students;
Assembly类其实就是程序集类。主要用来加载其他程序集,加载后才能用Type来使用其他程序集中的信息,如果想要使用不是自己程序集中的内容,需要先加载程序集(比如dll文件)。
三种加载程序集的函数:
▲一般用来加载本地目录下的其他程序集
Assembly assembly = Assembly.Load(“程序集名称”);
▲一般用来加载不在本地目录下的其他程序集
Assembly assembly = Assembly.LoadFrom(“包含程序集清单的文件的名称或路径”);
Assembly assembly = Assembly.LoadFile(“要加载的文件的完全限定路径”);
使用方法:
1、首先加载一个指定程序集:
Assembly assembly = Assembly.LoadFrom (@"C:\Users\TestDLL.dll");
2、再加载程序集中的一个类对象,之后才能使用反射
//得到dll中的Class1类
Type c = assembly.GetType("TestDLL.Class1");
object o = Activator.CreateInstance(c);
//调用Class1类中的Speak方法
MethodInfo speak = c.GetMethod("Speak");
speak.Invoke(o,null);