反射Reflection是.NET平台在运行时获取类型(包括类、接口、结构体、委托和枚举等类型)信息的重要机制,即从对象外部获取内部的信息,包括字段、属性、方法、构造函数和特性等。我们可以使用反射动态获取类型的信息,并利用这些信息动态创建对应类型的对象。反射的优点:提高程序的灵活性和扩展性,降低程序的耦合性,动态实例化对象。反射的缺点:性能消耗比直接获取类型信息要大,反射代码比普通代码复杂,维护性差。因此普通程序不建议使用反射,需要构建灵活性和扩展性较高的系统框架时才使用。
反射涉及到4个重要类和命名空间:System.Type、System.Attribute、System.Reflection和System.Reflection.Assembly,分别用于Type对象、类型特性Attribute、类型信息和程序集等相关操作。下面我将一一讲解各个核心类以及在反射中的用途。
System.Type
Type类是反射机制的核心,是反射获取类型信息的入口,通过该类可以从类型外部获取到内部的信息。Type对象是用来存储类的名字、命名空间、程序集、字段、属性和方法等类相关的信息。获取Type对象的方法有三个:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Test test = new Test();
// 第一种:新建一个要反射的类型对象test,然后调用test对象中GetType方法返回一个Type对象。
Type type = test.GetType();
// 第二种:直接将类型名传入typeof运算符得到type对象。
Type type = typeof(Test);
// 第三种:直接调用Type类中的静态方法GetType,但参数必须是完整类型名(命名空间+类名)的字符串格式。
Type type = Type.GetType("LearnTypeClass.Test")
Console.WriteLine(type.Name); // 输出类型的名字
Console.WriteLine(type.Namespace); // 输出类型所在命名空间
Console.WriteLine(type.Assembly); // 输出类型所在程序集
Console.WriteLine(type.Module); // 输出类型所在模块
|
C#中的每一个对象都有GetType方法,包括后面讲到的程序集Assembly。但由于GetType是基类System.Object的方法,必须实例化才能使用,因此需要通过类型的对象调用GetType方法来获得。也可采用第三种直接调用Type类中的静态方法GetType的方法。而typeof是运算符,可以直接调用,只不过typeof必须传入类的名称而不是对象,从而返回Type对象。
System.Attribute
Attribute是与特性相关的类,用于为类、接口、结构体、委托和枚举等类型添加声明性信息,用[]括起来置于类型上方。C#支持自定义特性,并使用反射访问特性。常见特性,如给类型上添加开发人员Review特性。下面我们将通过代码自定义一个开发人员Review特性并使用反射访问该特性。
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
using System;
namespace LearnReflection
{
// 首先定义特性类Author,需继承自System.Attribute基类。自定义类可以使用AttributeUsage特性设置目标类型,是否支持多个特性和特性继承。
// AttributeTargets参数可指定自定义特性的目标类型(包括类、接口、结构体、委托和枚举等类型),默认采用AttributeTargets.All全部目标类型。
// AllowMultiple参数表示是否可以在单一类型上使用多次特性,默认为false只能使用一次特性。
// Inherited参数表示是否支持特性继承,默认为true支持继承的子类具有改特性。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
public class Author: Attribute
{
private string name; // 私有字段一般为位置参数,使用时直接写参数指即可,如"Neo"。
public double version; // 公共字段和属性为命名参数,使用时必须带参数名和值,如"version = 1.1"。
public string Name
{
get { return this.name; }
set { this.name = value; }
}
public Author(string name)
{
this.name = name;
version = 1.0; // 为命名参数设置默认值,使用时不写该参数则采用默认值。
}
public string GetName()
{
return name;
}
}
// [Author("Neo")]等价于调用Author构造函数,name为Neo,version默认值为1.0。
// 多个特性也可以写在一行,如[Author("Neo"), Author("Neowyj", version = 1.1)]。
[Author("Neo")]
[Author("Neowyj", version = 1.1)]
class Test
{
public void TestFunc()
{
Console.WriteLine("TestFunc");
}
}
class Program
{
static void Main(string[] args)
{
// 获取Test类的所有属性
Type type = typeof(Test);
// Attribute.GetCustomAttributes方法返回Test类的一个特性属性数组。
Attribute[] attrs = Attribute.GetCustomAttributes(type);
// 返回每个特性的成员信息,包括字段、属性和方法等。
foreach (var attr in attrs)
{
Console.WriteLine(attr);
Type t = attr.GetType();
// 返回该对象的所有成员
foreach (var m in t.GetMembers())
{
Console.WriteLine(m);
}
Console.WriteLine();
}
}
}
}
// 输出结果包括系统自带的方法和属性。
// LearnReflection.Author
// System.String get_Name()
// Void set_Name(System.String)
// System.String GetName()
// Boolean Equals(System.Object)
// Int32 GetHashCode()
// System.Object get_TypeId()
// Boolean Match(System.Object)
// Boolean IsDefaultAttribute()
// System.String ToString()
// System.Type GetType()
// Void .ctor(System.String)
// System.String Name
// System.Object TypeId
// Double version
|
System.Reflection
Reflection是反射的核心命名空间,命名空间类似于JAVA中包,使用using指令调用。命名空间可以包含类、接口、结构体、委托和枚举等类型,这些类型又包括字段、属性和方法等。引入System.Reflection命名空间就可以使用反射相关的类。Reflection中常用的类包括:FieldInfo、PropertyInfo、MethodInfo、ConstructorInfo和Assembly类。
1
2
3
4
|
FieldInfo // 获取类型的字段信息,用Type对象的GetFields()方法获得,并返回一个FieldInfo数组。
PropertyInfo // 获取类型的字段信息,用Type对象的GetProperties()方法获得,并返回一个PropertyInfo数组。
MethodInfo // 获取类型的字段信息,用Type对象的GetMethods()方法获得,并返回一个MethodInfo数组。
Assembly // 用于程序集的反射操作,下个部分专门讲。
|
下面我们通过一个示例代码来获取类型相关的信息,并利用这些信息动态生成一个对象。
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
using System;
using System.Reflection;
namespace LearnReflection
{
public class NewClass
{
public string a;
public int b;
public string Name { get; set; }
public int Age { get; set; }
public NewClass(string m, int n)
{
a = m;
b = n;
}
public NewClass()
{
Console.WriteLine("调用构造函数");
}
public void Show()
{
Console.WriteLine("生成一个对象成功"+a+b+this.Name+this.Age);
}
}
class Program
{
static void Main(string[] args)
{
// 1、查看类中的构造函数
NewClass nc = new NewClass();
Type t = nc.GetType();
// 获取该类的所有构造函数
ConstructorInfo[] cis = t.GetConstructors();
// 构造函数在数组中的顺序就是构造函数在类中的定义顺序,所以该类先输出有参构造函数和参数,再输出默认构造函数和参数。
foreach (var ci in cis)
{
// 获取该类每个构造函数的所有参数
ParameterInfo[] pis1 = ci.GetParameters();
foreach (var pi1 in pis1)
{
Console.WriteLine(pi1.ParameterType.ToString() + " " + pi1.Name + ",");
}
Console.WriteLine();
}
// 2、用构造函数动态生成对象
// 用于生成对象的参数类型数组,第一个参数是string,第二个参数是int。
Type[] pt = new Type[2];
pt[0] = typeof(string);
pt[1] = typeof(int);
// 根据参数类型获取构造函数,有可能有多个构造函数。
ConstructorInfo ci2 = t.GetConstructor(pt); // 获取参数为string和int类型的构造函数
// object派生自System.Object类的实例对象,泛指C#中的一切对象,包括系统自定义和用户自定义的类型)的对象。
object[] objs = new object[2] { "Neo", 6 };
// 调用构造函数用Invoke,传递参数为objs
object obj = ci2.Invoke(objs);
// 测试调用结果是否成功,先转换类型再调用函数,不然就变成了先调用Show函数,再转换Show函数得到的结果。
((NewClass)obj).Show();
// 3、用Activator生成对象
// 给构造函数输入具体参数
object[] objs3 = new object[2] { "wyj", 6 };
// 用Activator的CreateInstance静态方法生成新对象。
// 以下三种都可以创建对象,CreateInstance方法的第一个参数表示创建对象的类型,后面的参数表示构造参数的参数,根据不同参数调用不同的构造函数。
// object o = Activator.CreateInstance(t, "neo", 1);
// object o = Activator.CreateInstance(t, objs3);
object o = Activator.CreateInstance(t);
((NewClass)o).Show();
// 4、用反射创建对象,并对字段、属性和方法进行操作。
object obj4 = Activator.CreateInstance(t);
// 获取指定字段并赋值,获取该类型所有字段用GetFields()方法,返回一个FieldInfo数组。
FieldInfo fi = t.GetField("a");
fi.SetValue(obj, "reflectionField");
FieldInfo fi2 = t.GetField("b");
fi2.SetValue(obj, 2);
// 获取指定属性并赋值,获取该类型所有属性用GetProperties()方法,返回一个PropertyInfo数组。
PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(obj, "wyj", null);
PropertyInfo pi2 = t.GetProperty("Age");
pi2.SetValue(obj, 20, null);
// 获取并调用指定方法,获取该类型所有方法用GetMethods()方法,返回一个MethodInfo数组。
MethodInfo mi = t.GetMethod("Show");
mi.Invoke(obj, null);
}
}
}
// 输出结果:
// 调用构造函数
// System.String m,
// System.Int32 n,
// 生成一个对象成功Neo60
// 调用构造函数
// 生成一个对象成功00
// 调用构造函数
// 生成一个对象成功reflectionField2wyj20
|
System.Reflection.Assembly
System.Reflection.Assembly是用来反射程序集的类。程序集Assembly是.NET应用程序的组成部分,程序集表示形式为.dll和.exe格式的文件。DLL(Dynamic Link Library,动态链接库)文件里包含大量程序代码,可被EXE可执行文件调用,更新应用程序时可直接更新.dll文件而不需要更新整个.exe安装包。程序集可以有多个C#文件组成,而程序集可以包含一个或多个模块Module,模块又可以包含多个类型。由于程序集Assembly是二进制文件格式,所以C#项目中的程序集文件一般在bin目录里。下面通过示例代码来反射程序集获取程序集里的类型。
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
48
49
50
51
52
53
54
|
using System;
using System.Reflection;
namespace LearnReflection
{
class AssemblyTest
{
}
class Program
{
static void Main(string[] args)
{
//Assembly ass = Assembly.GetExecutingAssembly(); // 获取当前程序集
//Assembly ass = Assembly.LoadFrom("/Users/neowyj/C#/DesignPattern/DesignPattern/bin/Debug/netcoreapp2.2/DesignPattern.dll"); // 加载绝对路径的程序集
Assembly ass = Assembly.LoadFrom("DesignPattern.dll"); // 直接加载当前程序集文件
// 类不是直接存储在Assembly里面,而是存在对应命名空间下,然后命名空间再存在一个或多个程序集里。
Type t = ass.GetType("LearnReflection.NewClass"); // 使用GetType方法从程序集里获取类型时,必须使用完整类型名(命名空间+类型)。
object o = Activator.CreateInstance(t); // 调用默认构造函数创建实例对象
MethodInfo mi = t.GetMethod("Show"); // 指定获取类型中的Show方法
mi.Invoke(o, null); // 调用该方法
object ot = null; // 只有类的成员变量才不需要必须初始化,其他变量必须初始化,比如函数内的局部变量。
// 获取该程序集中的所有类型时,不需要命名空间,它会获取同一程序集下不同C#文件和不同命名空间中的所有类型。
Type[] tss = ass.GetTypes();
foreach (var ts in tss)
{
Console.WriteLine(ts.Name);
// 获取该程序集下其他C#文件中的Test类型并实例化对象。
if (ts.Name == "Test")
{
// 反射Assembly中的其他类型时,即使当前程序并没有引用(调用)它,一样可以反射获取类的内部结构和创建类的实例对象。
ot = Activator.CreateInstance(ts);
}
}
((Test)ot).TestFunc();
}
}
}
// 输出结果:
// 调用构造函数
// 生成一个对象成功00
// Test
// Program2
// Author
// Test
// Program3
// NewClass
// Program2
// AssemblyTest
// Program
// TestFunc
|
本文通过介绍反射相关的重要类和命名空间来分析C#中的反射机制,并对反射相关的操作都进行了代码实现,这样理解更深刻。在后续的Unity学习中,我们会遇到C#反射在Unity中的应用,包括Unity编辑器扩展和用C#反射执行DLL文件等。
Author
Neo
LastMod
2020-07-13
License
Crative Commons 4.0