CS4L20——反射
本章代码关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Type object .GetType() typeof () Type.GetType() type.GetMembers() type.GetConstructors() type.GetConstructor() ConstructorInfo constructorInfo.Invoke() type.GetFields() type.GetField() FieldInfo fieldInfo.GetValue() fieldInfo.SetValue() type.GetMethods() type.GetMethod() MethodInfo methodInfo.Invoke() type.GetProperties() type.GetProperty() PropertyInfo propertyInfo.GetValue() propertyInfo.SetValue() type.IsEnum type.GetEnumNames() type.GetEnumName() Activator.CreateInstance() Assembly Assembly.Load() Assembly.LoadFrom() Assembly.LoadFile() assembly.GetTypes() assembly.GetType()
程序集
编译器
编译器是一种翻译程序,它用于将源语言程序翻译成目标语言程序
源语言程序:某种程序设计语言写成的,比如 C#、C、C++、Java 等语言写的程序
目标语言程序:二进制数标识的伪机器代码写的程序
C# 的 Roslyn 编译器就会将我们编写的 .cs
源代码文件编译为 .exe
或者 .dll
等可执行程序
程序集是经由编译器编译得到的,供进一步编译执行的那个中间产物
在 Windows 系统中,一般表现为后缀为 .dll
(库文件)或者是 .exe
(可执行文件)的格式
其中:
.dll
是动态库,它可以被其他程序调用其中的函数、类等等,我们也可以调用其中的库函数或者类型等等
.exe
是可执行文件,就是那些可以被双击点击启动的运行的应用程序,我们编写的代码最终就是被编译为 exe
再运行
简单来说:
程序集就是我们写的一个代码集合,我现在写的所有代码
最终都会被编译器翻译为一个程序集供别人使用
比如一个代码库文件(.dll
)或者一个可执行文件(.exe
)
元数据
元数据就是用来描述数据的数据,这个概念不仅仅用于程序上,在别的领域也有元数据
简单来说,程序中的类、类中的函数、变量等等信息就是 程序的元数据
有关程序以及类型的数据被称为 元数据,他们被保存在程序集中
也就是说,我们自定义的类,接口,结构体,函数等等,C# 自带的类型,都存储在元数据内部
反射
程序在运行时,可以查看其他程序集或者自身的元数据 ,一个运行的程序查看本身或者其他程序的元数据的行为就叫做反射
简单来说就是,在程序运行时,通过 反射 可以得到其他程序集或者自己程序集代码的各种信息
类 函数 变量 对象 等等,实例化它们,执行它们,操作它们
直观来说,通过反射,我们可以使用 字符串 去从元数据中获取我们想要的类,方法等等 ,然后调用他们
这个 字符串 可以是程序运行期间得到的,由外部传入的 ,而不一定是在源代码里写出来的,
之前我们调用一个类,一个方法,都是在源代码里写明调用的类和方法,
而通过反射,我们可以通过一个字符串变量去调用一个类或者方法
反射的作用
因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性:
程序在运行时得到的所有元数据,包括元数据的特性
程序运行时,实例化对象,操作对象
程序运行时创建新对象,用这些对象执行任务
例如,我们可以通过在配置文件里,例如 Json文件 等等,
写一个类的名字,然后读取这个配置文件,拿到类名的字符串,通过反射,我们就可以实例化甚至调用这个类
这样便不需要去源代码内自己写出类的调用,也可以调用一个类
又比如,我们可以在一个程序集内部,通过反射调用另一个程序集内的类,方法
以后学习了序列化和反序列化,可以加深对反射的作用的理解
注意!反射可以让我们通过更灵活的方式调用类和方法 ,但反射调用的性能一般是不如代码直接调用的
因此,除了必要的情况(例如程序运行时才能知道需要调用哪个类 ,外部代码随时可能变化 ,性能不敏感的情况),最好少用反射调用外部的类和方法
Type
Type
(类的信息类),它是反射功能的基础!它是访问元数据的主要方式。
使用 Type
的成员获取有关类型声明的信息,有关类型的成员(如构造函数、方法、字段、属性和类的事件)
简单来说,Type
本身是一个类,它记录了某个类的所有成员信息,包括成员变量,函数,构造函数,属性,事件等等
接下来的示例以下面的类作为反射获取的目标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 namespace ReflectionSample { class Test { private int i = 1 ; public int j = 0 ; public string str = "123" ; public Test () {} public Test (int i ) { this .i = i; } public Test (int i, string str ) : this (i ) { this .str = str; } public void Speak () { Console.WriteLine("[无参重载]说话内容是:" + str); } public void Speak (string speakText ) { Console.WriteLine("[有参重载]说话内容是:" + speakText); } public static int StaticTestFunc () { Console.WriteLine("静态方法调用测试" ); return 999 ; } public int Number { get => i; set => i = value ; } } }
获取 Type
通过 object 的 GetType() 获取 Type
因为它是 object 就有的方法,因此实际上所有的类和结构体都可以通过 GetType()
来获取其 Type
信息
1 2 3 int a = 42 ;Type type = a.GetType(); Console.WriteLine(type);
输出:
该方法可以获取一个类对象真正的 Type
,即使这个对象是父类装子类的变量,也可以得到它真正的类型
因此,如果一个方法传入的是一个 object
类型的参数,我们也可以通过 GetType()
得到其真正的类型 Type
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static void Main (){ int a = 42 ; string str = "aaa" ; Test testObj = new Test(); OutputType(a); OutputType(str); OutputType(testObj); } static void OutputType (object obj ){ Type type = obj.GetType(); Console.WriteLine(type); }
输出:
1 2 3 System.Int32 System.String Test
通过 typeof 关键字传入类名获取 Type
这个方法相当于直接获取某个类的 Type
值,它可以存储到一个变量内,可以参与传参,也可以用于比较
而原本我们是不能直接将类名去存储或者传参的,因为类名不是一个值
1 2 Type type = typeof (int ); Console.WriteLine(type);
输出:
通过类名字符串去获取类型
注意!类名必须包含命名空间,不然找不到!
通过类名的字符串获取一个类的 Type
信息是反射中非常重要的功能之一,
通过这个功能,我们才可以在程序运行时通过外部传入的字符串来获取一个类的 Type
进而通过这个 Type
去调用对应的类的各种成员(变量,方法等),
这样调用一个类就不需要一定要在代码内写清楚调用语句,提高了程序的灵活性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 namespace TestSpace { class Test {} } class Program { static void Main () { GetTypeFromString("Int32" ); GetTypeFromString("System.Int32" ); GetTypeFromString("TestSpace.Test" ); } static void GetTypeFromString (string typeName ) { Type? type = Type.GetType(typeName); if (type != null ) { Console.WriteLine("得到类型:" + type); } else { Console.WriteLine("没有从字符串内得到类型" ); } } }
输出:
1 2 3 没有从字符串内得到类型 得到类型:System.Int32 得到类型:TestSpace.Test
关于不同的Type变量
如果多个 Type
变量存储的是同一个类的类型信息,那么这几个 Type
变量肯定是存储的同一个地址信息
因此,我们就可以通过 !=
,==
来判断两个 Type
是不是存储的同一个类的类型信息,
也可以判断一个变量的类型或者字符串是不是某一个类的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Program { static void Main () { Type type = typeof (int ); Type? type2 = Type.GetType("System.Int32" ); if (type == type2) { Console.WriteLine("两个Type相等" ); } int i = 9 ; long l = 10 ; CheckType(i); CheckType(l); } static void CheckType (object obj ) { Type type = obj.GetType(); if (type == typeof (int )) { Console.WriteLine("传入的变量类型是int类型" ); } else { Console.WriteLine("传入的变量类型不是int类型" ); } } }
输出:
1 2 3 两个Type相等 传入的变量类型是int 类型 传入的变量类型不是int 类型
得到类所在的程序集信息
可以通过 Type
可以得到类型所在的程序集信息,Assembly
就是程序集类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.Reflection;class Program { static void Main () { int a = 42 ; Type type = a.GetType(); Console.WriteLine(type); Type type2 = typeof (int ); Console.WriteLine(type2); Type? type3 = Type.GetType("System.Int32" ); Console.WriteLine(type3); Assembly assembly = type.Assembly; Console.WriteLine(assembly); Console.WriteLine(type2.Assembly); Console.WriteLine(type3?.Assembly); } }
输出:
1 2 3 4 5 6 System.Int32 System.Int32 System.Int32 System.Private.CoreLib, Version=8.0 .0 .0 , Culture=neutral, PublicKeyToken=7 cec85d7bea7798e System.Private.CoreLib, Version=8.0 .0 .0 , Culture=neutral, PublicKeyToken=7 cec85d7bea7798e System.Private.CoreLib, Version=8.0 .0 .0 , Culture=neutral, PublicKeyToken=7 cec85d7bea7798e
获取类中的所有公共成员
我们可以通过 Type
获取该类的所有成员信息,成员信息的类型是 MemberInfo
type.GetMembers()
可以一次获取所有的成员信息数组
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 9 10 11 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } MemberInfo[] infos = type.GetMembers(); for (int i = 0 ; i < infos.Length; i++){ Console.WriteLine("ReflectionSample.Test 成员" + i + ": " + infos[i]); }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ReflectionSample.Test 成员0 : Void Speak () ReflectionSample.Test 成员1: Void Speak (System.String ) ReflectionSample.Test 成员2: Int32 StaticTestFunc () ReflectionSample.Test 成员3: Int32 get_Number () ReflectionSample.Test 成员4: Void set_Number (Int32 ) ReflectionSample.Test 成员5: System.Type GetType () ReflectionSample.Test 成员6: System.String ToString () ReflectionSample.Test 成员7: Boolean Equals (System.Object ) ReflectionSample.Test 成员8: Int32 GetHashCode () ReflectionSample.Test 成员9: Void .ctor () ReflectionSample.Test 成员10: Void .ctor (Int32 ) ReflectionSample.Test 成员11: Void .ctor (Int32, System.String ) ReflectionSample.Test 成员12: Int32 Number ReflectionSample.Test 成员13: Int32 j ReflectionSample.Test 成员14: System.String str
可见,Test
类的所有成员都被获取到了,包括成员变量、方法、属性,其中 .ctor
是构造方法
而 get_Number()
和 set_Number(Int32)
是属性 Number
编译后生成的两个方法,可见,属性本质上就是两个方法
公共构造函数信息
我们可以通过 Type
获取该类的所有构造函数,构造函数成员信息的类型是 ConstructorInfo
通过 ConstructorInfo
构造函数信息,我们可以反射调用其对应的构造方法,来实例化一个类
得到所有公共构造函数信息
type.GetConstructors()
可以一次获取所有的构造函数信息数组
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 9 10 11 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } ConstructorInfo[] infos = type.GetConstructors(); for (int i = 0 ; i < infos.Length; i++){ Console.WriteLine("ReflectionSample.Test 构造方法" + i + ": " + infos[i]); }
输出:
1 2 3 ReflectionSample.Test 构造方法0 : Void .ctor() ReflectionSample.Test 构造方法1 : Void .ctor(Int32) ReflectionSample.Test 构造方法2 : Void .ctor(Int32, System.String)
得到特定的公开构造函数信息
type.GetConstructor()
可以获取一个特定的构造函数,需要传入 Type[]
数组,
也就是说,获取某个特定的构造函数,需要传入参数列表的各个参数类型去获取,
例如,无参构造函数,就可以直接传入一个 Type[0]
,也就是没有参数,
又比如,对于参数列表为 (int i, string s)
的构造函数,就需要传入包括 Int32
和 String
的 Type
数组(顺序不允许错!)
1 2 3 4 5 6 7 8 9 10 11 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } ConstructorInfo? info1 = type.GetConstructor(new Type[0 ]); ConstructorInfo? info2 = type.GetConstructor(new Type[] { typeof (int ), typeof (string ) });
通过公开构造函数信息实例化对象
得到 ConstructorInfo
后,使用 Invoke()
即可通过反射实例化类,
其中 Invoke()
需要传入 object[]
数组,数组内容就是要传递到构造函数的参数,如果是无参构造函数传入 null
即可
Invoke()
返回的是一个 object
对象,我们需要手动转换成对应的类型
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } ConstructorInfo? info1 = type.GetConstructor(new Type[0 ]); if (info1 == null ){ Console.WriteLine("ReflectionSample.Test 构造方法获取失败" ); return ; } object obj1 = info1.Invoke(null );Console.WriteLine((obj1 as ReflectionSample.Test)?.Number); ConstructorInfo? info2 = type.GetConstructor(new Type[] { typeof (int ), typeof (string ) }); if (info2 == null ){ Console.WriteLine("ReflectionSample.Test 构造方法获取失败" ); return ; } object obj2 = info2.Invoke(new object [] { 123 , "通过反射调用有参构造函数" });Console.WriteLine((obj2 as ReflectionSample.Test)?.Number); Console.WriteLine((obj2 as ReflectionSample.Test)?.str);
输出:
可以看到,我们通过反射调用构造函数,实例化了两个对象
公共成员变量信息
我们可以通过 Type
获取该类的公共成员变量,公共成员变量信息的类型是 FieldInfo
通过 FieldInfo
公共成员变量信息,我们可以反射调用某个对象的公共成员变量,对该对象的公共成员变量进行读取或赋值操作
FieldInfo
也可以获取枚举类型的枚举值,详见下文:反射获取枚举值
得到所有成员变量
type.GetFields()
可以一次获取所有的公共成员变量信息数组
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 9 10 11 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } FieldInfo[] infos = type.GetFields(); for (int i = 0 ; i < infos.Length; i++){ Console.WriteLine("ReflectionSample.Test 公开变量" + i + ": " + infos[i]); }
输出:
1 2 ReflectionSample.Test 公开变量0 : Int32 j ReflectionSample.Test 公开变量1 : System.String str
得到指定名称的成员变量
type.GetField()
可以获取特定的公共成员变量信息,我们需要传入这个成员变量的名字的字符串
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } FieldInfo? info = type.GetField("str" );
通过反射获取对象的某个变量的值
得到 FieldInfo
后,使用 GetValue()
即可通过获取对象的成员变量的值
GetValue()
需要传入一个类对象(且这个类对象的 Type
必须包含此 FieldInfo
),
返回值就是传入的类对象中,FieldInfo
对应的那个成员变量的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } object ? testObj = type.GetConstructor(new Type[0 ])?.Invoke(null );FieldInfo? info = type.GetField("str" ); if (testObj != null && info != null && testObj is ReflectionSample.Test){ ((ReflectionSample.Test)testObj).str = "aaaa" ; Console.WriteLine(info.GetValue(testObj)); }
输出:
通过反射设置对象的某个变量的值
得到 FieldInfo
后,使用 SetValue()
即可对类对象的成员变量赋值
SetValue()
需要传入一个类对象和要赋的值(且这个类对象的 Type
必须包含此 FieldInfo
),
SetValue()
就会将传入的类对象的 FieldInfo
对应的那个成员变量的值修改为传入的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } object ? testObj = type.GetConstructor(new Type[0 ])?.Invoke(null );FieldInfo? info = type.GetField("str" ); if (testObj != null && info != null ){ info.SetValue(testObj, "通过反射修改的值!" ); Console.WriteLine(info.GetValue(testObj)); }
输出:
公共成员方法信息
我们可以通过 Type
获取该类的公共成员方法,公共成员方法信息的类型是 MethodInfo
通过 MethodInfo
公共成员方法信息,我们可以反射调用某个对象的公共成员方法
得到所有公开成员方法信息
type.GetMethods()
可以一次获取所有的公共成员变量信息数组
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 9 10 11 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } MethodInfo[] infos = type.GetMethods(); for (int i = 0 ; i < infos.Length; i++){ Console.WriteLine("ReflectionSample.Test 公开方法" + i + ": " + infos[i]); }
输出:
1 2 3 4 5 6 7 8 9 ReflectionSample.Test 公开方法0 : Void Speak () ReflectionSample.Test 公开方法1: Void Speak (System.String ) ReflectionSample.Test 公开方法2: Void StaticTestFunc () ReflectionSample.Test 公开方法3: Int32 get_Number () ReflectionSample.Test 公开方法4: Void set_Number (Int32 ) ReflectionSample.Test 公开方法5: System.Type GetType () ReflectionSample.Test 公开方法6: System.String ToString () ReflectionSample.Test 公开方法7: Boolean Equals (System.Object ) ReflectionSample.Test 公开方法8: Int32 GetHashCode ()
值得一提的是,由属性生成的两个 get
和 set
方法也是包含在该类型的公开方法里的
得到特定的公开成员方法信息
type.GetMethod()
可以获取一个特定的公开成员方法,需要传入函数名,
如果函数存在重载,还需要传入参数列表的对应 Type[]
数组,根据参数类型列表以确定需要获取的是哪个函数重载
如果是无参方法,就可以直接传入一个 Type[0]
,也就是没有参数,
如果是有参方法,例如参数列表为 (int i, string s)
的方法,就需要传入包括 Int32
和 String
的 Type
数组(顺序不允许错!)
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 9 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } MethodInfo? info1 = type.GetMethod("StaticTestFunc" ); MethodInfo? info2 = type.GetMethod("Speak" , new Type[0 ]); MethodInfo? info3 = type.GetMethod("Speak" , new Type[1 ] { typeof (string ) });
通过公开成员方法信息调用方法
得到 MethodInfo
后,使用 Invoke()
即可通过调用类对象的方法
Invoke()
需要 要调用方法的对象 和 object[]
数组,其中数组内容就是要传递到构造函数的参数
如果是静态方法,第一个参数传入 null
,如果是无参方法,第二个参数传入 null
即可
如果调用的方法有返回值,则会返回一个 object
对象,如果无返回值则返回 null
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } MethodInfo? info1 = type.GetMethod("StaticTestFunc" ); MethodInfo? info2 = type.GetMethod("Speak" , new Type[0 ]); MethodInfo? info3 = type.GetMethod("Speak" , new Type[1 ] { typeof (string ) }); object ? testObj = type.GetConstructor(new Type[0 ])?.Invoke(null );if (testObj != null ){ object ? result = info1?.Invoke(null , null ); Console.WriteLine("result: " + (result == null ? "NULL" : result)); result = info2?.Invoke(testObj, null ); Console.WriteLine("result: " + (result == null ? "NULL" : result)); result = info3?.Invoke(testObj, new object [] { "通过反射调用的方法" }); Console.WriteLine("result: " + (result == null ? "NULL" : result)); }
输出:
1 2 3 4 5 6 静态方法调用测试 999 [无参重载]说话内容是:123 NULL [有参重载]说话内容是:通过反射调用的方法 NULL
属性信息
我们可以通过 Type
获取该类的属性,属性信息的类型是 PropertyInfo
通过 PropertyInfo
属性信息,我们可以反射调用某个对象的属性,对该对象的属性进行读取或赋值操作
以这个类为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 namespace ReflectionSample { class PropertyTest { private int i = 1 ; public string str = "123" ; public int Number { get => i; set => i = value ; } public string Text { get { Console.WriteLine("读取属性" ); return str; } set { str = value ; Console.WriteLine("写入属性" ); } } } }
得到所有成员属性
type.GetProperties()
可以一次获取所有的属性信息数组
1 2 3 4 5 6 7 8 9 10 11 Type? type = Type.GetType("ReflectionSample.PropertyTest" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } PropertyInfo[] infos = type.GetProperties(); for (int i = 0 ; i < infos.Length; i++){ Console.WriteLine("ReflectionSample.Test 属性" + i + ": " + infos[i]); }
输出:
1 2 ReflectionSample.Test 属性0 : Int32 Number ReflectionSample.Test 属性1 : System.String Text
得到指定名称的属性
type.GetProperties()
可以获取特定的属性信息,我们需要传入这个属性的名字的字符串
示例(下面的代码中用于反射的类已经在上文定义):
1 2 3 4 5 6 7 8 Type? type = Type.GetType("ReflectionSample.PropertyTest" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } PropertyInfo? info1 = type.GetProperty("Number" ); PropertyInfo? info2 = type.GetProperty("Text" );
通过反射获取对象的某个属性的值
得到 PropertyInfo
后,使用 GetValue()
即可通过获取对象的属性的值
GetValue()
需要传入一个类对象(且这个类对象的 Type
必须包含此 PropertyInfo
),
返回值就是传入的类对象中,PropertyInfo
对应的那个属性的值
1 2 3 4 5 6 7 8 9 10 11 12 13 Type? type = Type.GetType("ReflectionSample.PropertyTest" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } object ? testObj = type.GetConstructor(new Type[0 ])?.Invoke(null );PropertyInfo? info1 = type.GetProperty("Number" ); Console.WriteLine(info1?.GetValue(testObj)); PropertyInfo? info2 = type.GetProperty("Text" ); Console.WriteLine(info2?.GetValue(testObj));
输出:
通过反射设置对象的某个变量的值
得到 PropertyInfo
后,使用 SetValue()
即可对类对象的属性赋值
SetValue()
需要传入一个类对象和要赋的值(且这个类对象的 Type
必须包含此 PropertyInfo
),
SetValue()
就会将传入的类对象的 PropertyInfo
对应的那个属性的值修改为传入的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Type? type = Type.GetType("ReflectionSample.PropertyTest" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } object ? testObj = type.GetConstructor(new Type[0 ])?.Invoke(null );if (testObj != null ){ PropertyInfo? info1 = type.GetProperty("Number" ); info1?.SetValue(testObj, 9999999 ); Console.WriteLine(info1?.GetValue(testObj)); PropertyInfo? info2 = type.GetProperty("Text" ); info2?.SetValue(testObj, "测试反射写入" ); Console.WriteLine(info2?.GetValue(testObj)); }
输出:
1 2 3 4 9999999 写入属性 读取属性 测试反射写入
枚举相关
以这个枚举为例:
1 2 3 4 5 6 7 namespace ReflectionSample { enum EnumTest { A, B, C, D, E, F } }
判断类型是否是枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Type? type = Type.GetType("ReflectionSample.EnumTest" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } if (type.IsEnum){ Console.WriteLine("是枚举类型" ); } else { Console.WriteLine("不是枚举类型" ); }
输出:
获取所有枚举名
type.GetEnumNames()
可以一次该获取该类型的所有枚举名,如果此类型不是枚举类型,则报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Type? type = Type.GetType("ReflectionSample.EnumTest" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } if (type.IsEnum){ string [] enumNames = type.GetEnumNames(); for (int i = 0 ; i < enumNames.Length; i++) { Console.WriteLine("enum " + i + ": " + enumNames[i]); } }
输出:
1 2 3 4 5 6 enum 0 : Aenum 1 : Benum 2 : Cenum 3 : Denum 4 : Eenum 5 : F
获取对象的枚举名
type.GetEnumName()
可以用于获取枚举对象的名字,可用于得到枚举值但是不知道其名字的情况
1 2 3 4 5 6 7 8 9 10 11 12 Type? type = Type.GetType("ReflectionSample.EnumTest" ); if (type == null ){ Console.WriteLine("类型获取失败!" ); return ; } int enumValue = 1 ;if (type.IsEnum){ string ? name = type.GetEnumName(enumValue); Console.Write(name != null ? ("Enum Name: " + name) : "Enum value is null" ); }
输出:
反射获取枚举值
获取某个枚举的枚举值实际上和获取公开成员方法一样,都是使用 type.GetField()
来获取,
其中 type.GetField()
需要先传入枚举名,然后从获取到的 fieldInfo.GetValue(null)
来获取枚举值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Type? testEnum = Type.GetType("ReflectionSample.EnumTest" ); if (testEnum == null ){ Console.WriteLine("类型获取失败!" ); return ; } FieldInfo[] enumValues = testEnum.GetFields(); for (int i = 0 ; i < enumValues.Length; i++){ Console.WriteLine(enumValues[i]); } FieldInfo? enumValueInfo = testEnum.GetField("A" ); object ? enumValue = enumValueInfo?.GetValue(null );if (enumValue != null ){ string ? name = testEnum.GetEnumName(enumValue); Console.WriteLine(name != null ? ("Enum Name: " + name) : "Enum value is null" ); }
输出:
1 2 3 4 5 6 7 Int32 value__ ReflectionSample.EnumTest A ReflectionSample.EnumTest B ReflectionSample.EnumTest C ReflectionSample.EnumTest D ReflectionSample.EnumTest E ReflectionSample.EnumTest F
其他
获取事件信息:
获取事件信息的方法:type.GetEvent()
,type.GetEvents()
事件信息:EventInfo
获取接口信息:
获取实现接口的方法:type.GetInterface()
,type.GetInterfaces()
该类实现的接口的信息:InterfaceInfo
Activator
用于快速实例化对象的类,用于将 Type
对象快捷实例化为对象,先得到 Type
,然后直接实例化一个对象
原来我们需要通过 Type
去获取构造函数信息,再通过函数构造信息去反射实例化对象,
接下来的示例以下面的类作为反射获取的目标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 namespace ReflectionSample { class Test { private int i = 1 ; public int j = 0 ; public string str = "123" ; public Test () {} public Test (int i ) { this .i = i; } public Test (int i, string str ) : this (i ) { this .str = str; } public void Speak () { Console.WriteLine("[无参重载]说话内容是:" + str); } public void Speak (string speakText ) { Console.WriteLine("[有参重载]说话内容是:" + speakText); } public static int StaticTestFunc () { Console.WriteLine("静态方法调用测试" ); return 999 ; } public int Number { get => i; set => i = value ; } } class Test1 {}; class Test2 {}; interface Itest {}; }
Activator 实例化对象
使用 Activator.CreateInstance()
(返回值是 object
)可以直接从一个 Type
实例化对象
如果调用的是无参构造,则 Activator.CreateInstance()
只需要传入一个需要实例化对象的 Type
对象即可
1 2 3 4 5 6 7 8 9 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("获取类型失败!" ); return ; } ReflectionSample.Test? obj = Activator.CreateInstance(type) as ReflectionSample.Test; Console.WriteLine(obj?.str);
输出:
如果调用的是有参构造,Activator.CreateInstance()
需要先传入一个需要实例化对象的 Type
对象,再在后续直接对照参数传入对象
(Activator.CreateInstance()
参数列表是变长参数,因此可以接收足够多的传入到构造函数的参数)
注意!type
参数后边传入的参数必须和要 type
的构造函数的参数列表对应,如果后续传入的参数有缺失、多余,或者错误,都会直接导致报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Type? type = Type.GetType("ReflectionSample.Test" ); if (type == null ){ Console.WriteLine("获取类型失败!" ); return ; } ReflectionSample.Test? obj = Activator.CreateInstance(type) as ReflectionSample.Test; Console.WriteLine("1: " + obj?.str); ReflectionSample.Test? obj1 = Activator.CreateInstance(type, 99 ) as ReflectionSample.Test; Console.WriteLine("2: " + obj1?.Number); ReflectionSample.Test? obj2 = Activator.CreateInstance(type, 888 , "ActivatorTest" ) as ReflectionSample.Test; Console.WriteLine("3.Number: " + obj1?.Number); Console.WriteLine("3.str: " + obj2?.str);
输出:
1 2 3 4 1 : 123 2 : 99 3. Number: 99 3. str: ActivatorTest
创建类库
类库,顾名思义是类的集合仓库,类库项目会编译出 dll
文件,它不会直接运行,而是被其他的项目调用其中的类与方法
我们可以将一些通用的类和方法作为一个模块放到类库工程内,然后被其他的类调用,这样,这些通用的类和方法就可以跨工程使用
将不同模块的代码划分到不同工程,也可以减少各个工程的编译时间,因为一个工程所需要编译的代码量减少了
Visual Studio 可以直接选择 类库 来创建类库工程
如果不使用 Visual Studio,也可以使用终端(cmd 或 powershell)去创建类库,在需要创建工程的路径下使用如下命令即可
Assembly
程序集类,主要用来加载其它程序集,加载后才能用 Type
来使用其它程序集中的信息
如果想要使用不是自己程序集中的内容,需要先加载程序集,比如 .dll
文件(库文件)
简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类三种加载程序集的函数
加载在同一文件下的其它程序集
1 Assembly asembly1 = Assembly.Load("程序集名称" );
加载不在同一文件下的其它程序集
1 2 Assembly asembly2 = Assembly.LoadFrom("包含程序集清单的文件的名称或路径" ); Assembly asembly3 = Assembly.LoadFile("要加载的文件的完全限定路径" );
假设,目前存在如下的类库,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 namespace GetAssemblyTest { class Test { private int i = 1 ; public int j = 0 ; public string str = "123" ; public Test () {} public Test (int i ) { this .i = i; } public Test (int i, string str ) : this (i ) { this .str = str; } public void Speak () { Console.WriteLine("[无参重载]说话内容是:" + str); } public void Speak (string speakText ) { Console.WriteLine("[有参重载]说话内容是:" + speakText); } public static int StaticTestFunc () { Console.WriteLine("静态方法调用测试" ); return 999 ; } public int Number { get => i; set => i = value ; } } }
这个类库工程处于另一个工程内,编译出来的 dll
文件位于:E:\CodeField\CSharpProjects\GetAssemblyTest\bin\Debug\net8.0
如果要在不直接引用这个工程的情况下调用上面的类,就可以通过 Assembly
这个类来读取程序集,进而获取其 Type
,再反射调用各种成员
获取程序集下所有的类型
使用 assembly.GetTypes()
可以获取一个程序集下的所有类型
1 2 3 4 5 6 Assembly assembly = Assembly.LoadFrom("E:/CodeField/CSharpProjects/GetAssemblyTest/bin/Debug/net8.0/GetAssemblyTest" ); Type[] types = assembly.GetTypes(); for (int i = 0 ; i < types.Length; i++){ Console.WriteLine("type" + i + ": " + types[i]); }
输出:
1 2 3 4 type0: GetAssemblyTest.Test type1: GetAssemblyTest.Test1 type2: GetAssemblyTest.Test2 type3: GetAssemblyTest.Itest
获取程序集下的某一个类型
使用 assembly.GetType()
传入类名,获取程序集下类名对应的类型 Type
接下来就可以通过反射,去反射实例化,调用其中的各种成员方法等等
(注意!如果工程内没有引用外部的类库工程,我们不能直接写代码来调用外部类的方法,因为无法直接找到类型)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Assembly assembly = Assembly.LoadFrom("E:/CodeField/CSharpProjects/GetAssemblyTest/bin/Debug/net8.0/GetAssemblyTest" ); Type? testType = assembly.GetType("GetAssemblyTest.Test" ); if (testType == null ){ Console.WriteLine("类型获取失败!" ); return ; } object ? test = Activator.CreateInstance(testType);MethodInfo? info1 = testType.GetMethod("StaticTestFunc" ); MethodInfo? info2 = testType.GetMethod("Speak" , new Type[0 ]); MethodInfo? info3 = testType.GetMethod("Speak" , new Type[1 ] { typeof (string ) }); if (test != null ){ object ? result = info1?.Invoke(null , null ); Console.WriteLine("result: " + (result == null ? "NULL" : result)); result = info2?.Invoke(test, null ); Console.WriteLine("result: " + (result == null ? "NULL" : result)); result = info3?.Invoke(test, new object [] { "通过反射调用的方法" }); Console.WriteLine("result: " + (result == null ? "NULL" : result)); }
输出:
1 2 3 4 5 6 静态方法调用测试 result: 999 [无参重载]说话内容是:123 result: NULL [有参重载]说话内容是:通过反射调用的方法 result: NULL