一、概念
- 反射(Reflection)在C#中是一种非常重要的特性,它为开发者提供了在运行时获取和操作关于类型、成员、属性、方法等的详细信息的能力。通过反射,开发者可以在程序运行期间动态地创建对象、调用方法、设置属性值以及进行其他多种操作,而不需要事先在代码中硬编码这些操作。
- C#中的反射API主要集中在System.Reflection命名空间下,该命名空间包含了多个用于反射的类和接口。
1.1常用的反射类型:
- Assembly(程序集):Assembly类用于表示一个程序集,可以加载和卸载程序集,以及获取程序集中的类型、资源等信息。
- Type(类型):Type类表示程序集中的任意类型,可以是类、接口、枚举、结构等。通过Type类可以获取类型的成员信息,如方法和属性等。
- MethodInfo(方法信息):MethodInfo类提供了关于特定方法的信息,包括方法的名称、参数、返回类型等,还可以调用该方法。
- PropertyInfo(属性信息):PropertyInfo类用于表示属性,可以获取或设置属性的值。
- FieldInfo(字段信息):FieldInfo类用于表示字段,可以获取或设置字段的值。
1.2常用场景
- 动态加载程序集:在运行时根据需要加载.dll或.exe文件,并访问其中的类型和成员。
- 晚期绑定和早期绑定:晚期绑定是指在程序运行时才确定要调用的方法或访问的属性,而早期绑定是在编译时就确定。反射可以实现晚期绑定。
- 对象序列化和反序列化:在序列化和反序列化过程中,反射可以动态地读取和写入对象的各个属性。
- 实现插件系统:通过反射可以加载和调用插件中的功能,而无需预先知道插件的具体实现细节。
- 单元测试和ORM(对象关系映射)技术:在单元测试中,反射可用于动态调用待测试的方法;在ORM技术中,反射则可用于动态访问和映射对象的属性至数据库表的列。
1.3程序集概念
- 程序集(Assembly)是.NET框架下代码编译的一个逻辑单元,用于组合相关的代码和类型,最终生成PE文件。
- 程序集是.NET应用程序的基本组成单元,它以可执行文件(.exe)或动态链接库文件(.dll)的形式存在。一个程序集可以包含一个或多个模块,每个模块又可以包括多个类型(如类、接口、枚举等)。程序集不仅包含编译后的IL(中间语言)代码,还包含描述程序集自身的元数据、资源文件等信息。
- 在软件开发过程中,一个项目通常对应一个程序集。例如,在Visual Studio中,一个解决方案可以包含多个项目,每个项目编译后都会生成一个程序集(一般是一个.dll文件)。这些程序集可以被其他项目引用,从而实现功能的复用和模块化开发。
二、Type类
2.1、概念
Type类是C#反射中的一个核心类,用于表示所有.NET类型的公共基类。通过Type类,可以访问类型的元数据,获取类型的成员信息,并进行实例化等操作。以下将详细探讨Type类的各个方面:
定义:Type类位于System.Reflection命名空间下,它提供了很多方法来获取类型信息、创建实例、执行方法等。
2.2、功能
通过Type类,可以检查类型是否为泛型类型、接口、委托等。同时,还可以获取类型的属性、方法、事件等成员信息。
2.3、使用
- 获取Type实例
静态方法:可以使用Type的静态方法如GetType、GetTypes等来获取类型信息。例如,typeof(int)返回表示int类型的Type对象。
动态实例:通过对象实例调用GetType()方法获取其类型。例如,一个字符串实例myString.GetType()将返回String类型的Type对象。 - 获取类型信息
属性和方法:使用Type对象的GetProperties和GetMethods方法可以分别获取类型的所有属性和方法。这些返回的PropertyInfo和MethodInfo对象提供了更详细的成员信息。
基类和接口:可以通过Type对象的BaseType属性获取其基类的类型,IsClass属性判断是否为类,Implements接口方法检查实现的接口。 - 创建类型实例
使用Activator类:Activator类提供了基于Type创建实例的方法,如CreateInstance。这允许在运行时动态创建对象。
使用ConstructorInfo:通过Type对象的GetConstructors方法获取构造函数,然后使用ConstructorInfo对象的Invoke方法创建实例。 - 调用类型方法
MethodInfo对象:通过Type对象的GetMethods获取方法列表,然后用MethodInfo对象的Invoke方法动态调用这些方法。
参数处理:MethodInfo的Invoke方法需要参数数组。如果方法需要的参数类型与传递的参数类型不匹配,需要进行适当的类型转换。 - 类型的兼容性和转换
类型兼容检查:IsAssignableFrom方法可以用来检查一个类型是否可以从另一个类型派生。例如,typeof(object).IsAssignableFrom(typeof(string))将返回true。
类型转换:Type类提供了几个方法来检查和执行类型转换,如IsInstanceOfType方法和ChangeType方法。
2.4、代码实例
// See https://aka.ms/new-console-template for more information
//Console.WriteLine("Hello, World!");
//反射
//Type类实例化
using System.Reflection;
Type type = typeof(Person);
Console.WriteLine("--------------------获取属性信息------------------------------");
Console.WriteLine(type.ToString());
//01、获取类中属性信息GetProperties(),返回的是PropertyInfo[]类型数组
PropertyInfo[] propertyInfos=type.GetProperties();
foreach (var item in propertyInfos)
{
Console.WriteLine(item.Name);
Console.WriteLine(item.Attributes);//还有其他特性可以获取
}
Console.WriteLine("-----------------------获取字段信息---------------------------");
//02、获取所有字段,只能获取共有字段,属性的第二种定义方式也属于私有字段
FieldInfo[] fieldInfos=type.GetFields();
foreach (var field in fieldInfos) {
Console.WriteLine(field.Name);
Console.WriteLine(field.FieldType);
}
/*结果:
iD
System.Int32
name
System.String*/
Console.WriteLine("-----------------------获取类的信息---------------------------");
//直接使用type调用相关方法
Console.WriteLine(type.Name);
Console.WriteLine(type.Attributes);
Console.WriteLine("-----------------------获取构造方法的信息---------------------------");
ConstructorInfo[] methodInfo =type.GetConstructors();
foreach (var item in methodInfo)
{
Console.WriteLine(item);//返回的是构造方法的签名,返回值与参数
}
/*
Void.ctor()
Void.ctor(Int32, System.String)*/
Console.WriteLine("-----------------------获取方法的信息---------------------------");
MethodInfo[] info = type.GetMethods();
foreach (var item in info)
{
Console.WriteLine(item.Name);
}
/// <summary>
/// 定义反射测试类
/// </summary>
class Person{
//01、定义公有字段
public int iD;
public string name;
//02、定义私有字段
private string Sex;
//03、定义属性,属性有两种定义方式
//方式1
public int Age { get; set; }
//方式2
private string address;
//04无参构造
public Person()
{
Console.WriteLine("无参构造");
}
//05有参构造
public Person(int iD, string name)
{
this.iD = iD;
this.name = name;
}
public string Address {
get { return this.address; }
set { this.address = value; }
}
}
三、Assembly类
3.1概念及使用
Assembly类在C#反射中是用于操作程序集的一个重要工具,它能够加载、枚举、和操纵程序集。以下是对Assembly类的详细解析:
- 加载程序集
- 静态加载方法:Assembly.Load方法可以加载一个程序集,通常用于加载同一目录下或全局程序集中的其它程序集[1]。例如,
Assembly assembly = Assembly.Load("AssemblyName");
。 - 动态加载方法:Assembly.LoadFrom和Assembly.LoadFile方法可以指定文件路径来加载程序集。例如,
Assembly assembly = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
[2]。
- 静态加载方法:Assembly.Load方法可以加载一个程序集,通常用于加载同一目录下或全局程序集中的其它程序集[1]。例如,
- 枚举程序集中的类型
- 获取类型信息:通过Assembly实例的GetTypes方法可以得到程序集中定义的所有类型的Type对象数组[3]。例如,
Type[] types = assembly.GetTypes();
。 - 创建类型实例:结合Activator.CreateInstance方法,可以用枚举到的Type动态创建类型的实例。例如,
object obj = Activator.CreateInstance(type);
[4]。
- 获取类型信息:通过Assembly实例的GetTypes方法可以得到程序集中定义的所有类型的Type对象数组[3]。例如,
- 调用方法与访问属性
- 调用方法:MethodInfo类可以用于获取方法和动态调用方法。例如,使用
MethodInfo method = type.GetMethod("MethodName");
来获取方法信息,然后使用method.Invoke(obj, parameters);
来调用它[4]。 - 访问属性:PropertyInfo类可以用来获取属性信息和动态设置或获取属性值。例如,
PropertyInfo property = type.GetProperty("PropertyName");
用来获取属性信息,然后property.SetValue(obj, value, index);
用于设置属性值[4]。
- 调用方法:MethodInfo类可以用于获取方法和动态调用方法。例如,使用
- 获取程序集信息
- 获取程序集名称和版本:Assembly实例的GetName方法可以获取程序集的完整名称、版本等信息。例如,
AssemblyName name = assembly.GetName();
[4]。 - 获取程序集元数据:可以使用Assembly实例的其他方法如GetCustomAttributes来检查程序集的特性和元数据[4]。
- 获取程序集名称和版本:Assembly实例的GetName方法可以获取程序集的完整名称、版本等信息。例如,
- 操纵程序集
- 创建新实例:除了加载已存在的程序集外,Assembly类还提供了CreateInstance方法用于创建程序集中特定类型的新实例[3]。例如,
object instance = assembly.CreateInstance("TypeName");
。 - 获取当前程序集:Assembly.GetExecutingAssembly方法可以获取当前正在执行的程序集,这在需要访问当前程序集中的资源或类型时非常有用[2]。例如,
Assembly currentAssembly = Assembly.GetExecutingAssembly();
。
- 创建新实例:除了加载已存在的程序集外,Assembly类还提供了CreateInstance方法用于创建程序集中特定类型的新实例[3]。例如,
3.2应用-Assembly拿到成员-Type操作成员
3.2.1、Assembly获取程序集中的类/接口
- VS中定义类库文件:demo49反射_数据,作为程序集文件
- 定义控制台应用程序:demo49反射_Assembly使用,通过反射 去获取指定程序集里面的程序 操作成员(注意:此文将并没有对程序集文件添加依赖,不可直接操作程序集文件中内容,需要使用反射相关技术获取程序集文件)
- 通过LoadFile获取程序集文件:通过loadFile+程序集的绝对路径的方式 获取到了要反射的程序集
Assembly ass= Assembly.LoadFile(@"E:\c#体系课\04c#高级\代码\demo49反射_数据\bin\Debug\net7.0\demo49反射_数据.dll");
4. 通过Assembly对象 自己程序的GetType获取程序集中指定的类型 成员 GetType()+类型全名称 [命名空间.类型名字]
//通过GetTypes() 获取程序集中 所以的成员 获取到的是 pubic + internal
Type type = ass.GetType("demo49反射_数据.Person");//获取单个
Type[] types = ass.GetTypes();//获取所有
但是有时候我们并不希望获取内部internal成员
//只获取程序集中被public修饰的成员,不要internal成员 Exported:出口
Type[] types = ass.GetExportedTypes();
foreach (var item in types)
{
Console.WriteLine(item.Name);
}
3.2.2、Type获取类中方法
- 添加数据,对Person类中添加需要测试的属性,方法等数据
public class Person
{
//01、定义属性
public string Name { get; set; }
public int Age { get; set; }
public char Gender { get; set; }
//02、定义方法
public static void SayHello()
{
Console.WriteLine("我是静态方法");
}
public void Sayhi()
{
Console.WriteLine("我是实例方法");
}
public int Add(int n1, int n2) {
return n1 + n2;
}
public Person()
{
}
//03、有参构造
public Person(string name,int age,char gender)
{
this.Name = name;
this.Age = age;
this.Gender = gender;
}
public void Test() {
Console.WriteLine("我是无参方法");
}
public void Test(string name) {
Console.WriteLine(name);
}
public void Test(int n1,int n2)
{
Console.WriteLine(n1+n2);
}
}
- 获取类中方法
//01、Assembly操作
Assembly ass= Assembly.LoadFile(@"D:\C#_Project\05C#高级代码\demo49反射_数据\bin\Debug\net7.0\demo49反射_数据.dll");
//02、Type操作
Type type = ass.GetType("demo49反射_数据.Person");
//获取所有方法
MethodInfo[] methodInfos = type.GetMethods();
foreach (var item in methodInfos)
{
Console.WriteLine(item.Name);
}
添加限定条件,只要静态方法
//如果我们要用BindingFlags.Static这个枚举 需要再加上一个public 枚举进行配合
MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
foreach (var item in methodInfos)
{
Console.WriteLine(item.Name);
}
//如果只要其中一个方法就可以用下面这种方式
MethodInfo method = type.GetMethod("SayHello");
3.2.3、执行获取的方法
如果执行静态方法 注意:不可以像调用委托或者事件一样通过加括号直接调用
间接调用 通过 invoke调用
//第一个参数 在调用成员的是很好 是否需要传入对象
//1.静态方法:类.
//2.实例方法:对象.
//第二个参数 调用方法的时候是否需要传入参数 如果需要就传入参数 不需要传入null就可以了
method.Invoke(null,null);
3.2.4、Activator创建对象(方法一)
对象创建唯一途径就是构造方法被调用
//01、获取程序集
Assembly ass= Assembly.LoadFile(@"D:\C#_Project\05C#高级代码\demo49反射_数据\bin\Debug\net7.0\demo49反射_数据.dll");
//02、获取程序集中需要的类
Type type = ass.GetType("demo49反射_数据.Person");
//03、创建person对象实例 在反射中 一般有2中方式创建对象
//1.调用构造函数
//2.通过Activator 动态创建实例对象
//Activator的底层 就是调用无参构造函数
object person = Activator.CreateInstance(type);
Console.WriteLine(person.ToString());
//调用实例方法,参数1:实例对象
method.Invoke(person,null);
注意:SayHello是静态方法直接通过类就可获取
3.2.5、构造方法创建对象(方法二)
//LoadFile传入的路径是绝对路径 不是相对路径
//1.通过loadFile+程序集的绝对路径的方式 获取到了要反射的程序集,从文件中找,不要直接属性复制
Assembly ass= Assembly.LoadFile(@"D:\C#_Project\05C#高级代码\demo49反射_数据\bin\Debug\net7.0\demo49反射_数据.dll");
Type type = ass.GetType("demo49反射_数据.Person");
//获取单个构造函数
//参数:string name,int age,char gender
ConstructorInfo constructor= type.GetConstructor(new Type[] { typeof(string),typeof(int),typeof(char)});
object person= constructor.Invoke(new object[] {"张三",10,'男'});
//间接调用 person返回的就是你创建的对象
PropertyInfo property= type.GetProperty("Name");
//通过GetValue()调用这个方法可以获取属性的值
object propertys= property.GetValue(person);
Console.WriteLine(propertys);//输出:张三
3.2.6、获取有参有返回值方法
//01、获取程序集
Assembly ass= Assembly.LoadFile(@"D:\C#_Project\05C#高级代码\demo49反射_数据\bin\Debug\net7.0\demo49反射_数据.dll");
//02、通过Assembly对象 自己程序的GetType获取程序集中指定的类型 成员 GetType()+类型全名称 [命名空间.类型名字]
Type type = ass.GetType("demo49反射_数据.Person");
//3.1、创建类对象
object person = Activator.CreateInstance(type);
//3.2、通过Type数组 判断调用方法的参数个数和参数类型 依次告诉我们编译器我们想要哪个方法
MethodInfo method = type.GetMethod("Test", new Type[] { });
MethodInfo method1 = type.GetMethod("Test", new Type[] { typeof(string) });
MethodInfo method2= type.GetMethod("Test", new Type[] { typeof(int), typeof(int) });
//04、调用重载方法 编译器无法直接从是否传递的参数来潘顿我们调用的是哪个方法
Console.WriteLine(method.Name);
method.Invoke(person,new object[] { });
Console.WriteLine(method1.Name);
method1.Invoke(person,new object[] {10 });
Console.WriteLine(method2.Name);
method2.Invoke(person,new object[] { 10,20 });