.Net中的反射

反射

使用反射,可以在程序运行时创建、调用和访问类型实例。 

程序集(Assembly)包含模块(Module)、模块包含类型(Type),而类型包含成员(Member)。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。

简单的来说,就是平常我们创建类实例,访问类成员函数、变量等,都是在开发环境中写好代码再去执行。使用反射后,可以在程序运行时,执行上述操作。

 

反射有时候也会用,这算是一个比较系统的总结了吧。

 

反射的典型用法

  • 使用 Assembly 来定义和加载程序集,加载程序集清单中列出的模块,以及在此程序集中定位一个类型并创建一个它的实例。

  • 使用 Module 发现信息,如包含模块的程序集和模块中的类。 还可以获取所有全局方法或模块上定义的其它特定的非全局方法。

  • 使用 ConstructorInfo 发现信息,如名称、参数、访问修饰符(如 public 或 private)和构造函数的实现详细信息(如 abstract或 virtual)。 使用 Type 的 GetConstructors 或 GetConstructor 方法来调用特定构造函数。

  • 使用 MethodInfo 发现信息,如名称、返回类型、参数、访问修饰符(如 public 或 private)和方法的实现详细信息(如 abstract 或 virtual)。 使用 Type 的 GetMethods 或 GetMethod 方法来调用特定方法。

  • 使用 FieldInfo 发现信息,如名称、访问修饰符(如 public 或 private)和一个字段的实现详细信息 (如 static);并获取或设置字段值。

  • 使用 EventInfo 发现信息(如名称、事件处理程序的数据类型、自定义特性、声明类型以及事件的反射的类型),并添加或删除事件处理程序。

  • 使用 PropertyInfo 发现信息(如名称、数据类型、声明类型,反射的类型和属性的只读或可写状态),并获取或设置属性值。

  • 使用 ParameterInfo 发现信息,如参数的名称、数据类型、参数是输入参数还是输出参数以及参数在方法签名中的位置。

  • 使用 CustomAttributeData 在于应用程序域的仅反射上下文中工作时发现有关自定义特性的信息。 CustomAttributeData 使你能够检查特性,而无需创建它们的实例。

 

System.Type

表示类型声明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型

 

Type是一个抽象的基类,实例化了一个Type对象,实际上就实例化了Type的一个派生类。

获取给定类型的Type引用有3种常用方式:

1、使用typeof运算符

1 var strType = typeof(string);

2、使用GetType()方法,所有的类都会从System.Object继承这个方法

1      string str = "HelloWorld";
2      var strType2 = str.GetType();

3、使用Type类的静态方法GetType

1 var t = Type.GetType("System.String");

 

System.Type常用属性

属性 返回值
Name 数据类型名
FullName 数据类型的完全限定名(包括命名空间)
Namespace 数据类型的命名空间
BaseType

该Type的直接基本类型

Module 该类型的模块 (DLL)
Assembly   该类型的 Assembly

 

 

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var t = Type.GetType("System.String");
 6             Console.WriteLine(t);
 7 
 8 
 9             Console.WriteLine("Name:" + t.Name);
10             Console.WriteLine("FullName:" + t.FullName);
11             Console.WriteLine("Namespce:" + t.Namespace);
12             Console.WriteLine("BaseType:" + t.BaseType);
13             Console.WriteLine("Assembly:" + t.Assembly.FullName);
14             Console.WriteLine("Module:" + t.Module.Name);
15         }
16     } 

 

Type类包含许多Is开头的属性,这些属性用于判断该Type是否属于该类型。常用的如下:

属性 说明
IsAbstract

是否是抽象类型

IsArray

是否为数组

IsByRef

是否是引用传递

IsClass

是否是一个类或委托

IsCOMObject

是否为 COM 对象

IsConstructedGenericType

是否表示构造的泛型类型的值

IsContextful

指示 Type 在上下文中是否可以被承载

IsEnum

是否是枚举

IsExplicitLayout

是否放置在显式指定的偏移量处的值

IsGenericMethodParameter

是否表示泛型方法定义中的类型参数

IsGenericParameter

是否表示泛型类型或方法的定义中的类型参数

IsGenericType

是否是泛型类型

IsGenericTypeDefinition

是否表示可以用来构造其他泛型类型的泛型类型定义

IsGenericTypeParameter

是否表示泛型类型定义中的类型参数

IsImport

是否应用了 ComImportAttribute 属性

IsInterface

是否是接口

IsLayoutSequential

是否按顺序(定义顺序或发送到元数据的顺序)放置的值

IsMarshalByRef

是否按引用进行封送

IsNested

是否表示其定义嵌套在另一个类型的定义之内的类型的值

IsNestedAssembly

是否是嵌套的并且只能在它自己的程序集内可见

IsPointer

是否是指针

IsPrimitive

是否为基元类型之一

IsPublic

是否声明为公共类型

IsSealed

是否声明为密封的

IsSerializable

是否为可序列化的

IsValueType

是否为值类型

IsVisible

是否可由程序集之外的代码访问的值

 

System.Type常用方法

System.Type的大多数方法都用于获取对应数据类型的成员信息:构造函数、属性、方法和事件等。

方法 说明
GetConstructors 获取所有公共构造函数
GetEvents 获取所有公共事件所有公共事件
GetFields 获取所有公共字段
GetMembers 获取所有公共成员
GetMethods 获取所有公共方法
GetProperties 获取所有公共属性

 

这里我们用WPF的Button类来进行演示:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace ReflectionDemo
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             var buttonType = typeof(System.Windows.Controls.Button);
14 
15             var ctors = buttonType.GetConstructors();
16             var events = buttonType.GetEvents();
17             var fields = buttonType.GetFields();
18             var members = buttonType.GetMembers();
19             var methods = buttonType.GetMethods();
20             var properties = buttonType.GetProperties();
21 
22             Console.WriteLine(buttonType.Assembly.FullName);
23             PrintResult(ctors, "Constructors");
24             PrintResult(events, "Events");
25             PrintResult(fields, "Fields");
26             PrintResult(members, "Members");
27             PrintResult(methods, "Methods");
28             PrintResult(properties, "Properties");
29         }
30 
31         static void PrintResult(object[] array, string title = "")
32         {
33             Console.WriteLine(title);
34             foreach (var item in array)
35             {
36                 Console.WriteLine(item);
37             }
38             Console.WriteLine();
39             Console.WriteLine();
40         }
41     }
42 }

 

此外,System.Type还提供了单数形式的方法,如t.GetMethod()、t.GetEvent()。单数形式的方式用于返回指定名称的值。

如:

1 var buttonType = typeof(System.Windows.Controls.Button);
2 var clickEvent = buttonType.GetEvent("Click");

 

在前面的示例代码中,我们输出 了string类型的Assembly,如下:

1 Assembly:mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

 

mscorlib.dll是我们引用的程序集,如果需要加载外部 程序集并获取类型信息,就需要使用Assembly类

 

System.Reflection.Assembly

Assembly类允许访问给定程序集的元数据,同时它也包含可以加载程序集的方法。

 

要将程序集加载到正在运行的程序中,可以通过两个方法:Assembly.Load()Assembly.LoadFrom()

这两个方法的区别是:

Assembly.Load()的参数是指定程序集名称,运行库会在各个位置(运行目录和GAC)搜索该程序集,试图找到该程序集。

Assembly.LoadFrom()的参数是指定程序集的完整路径,它不会在其它位置搜索该程序集

 

 

如何判断类型是否实现了某个接口

1 typeof(XXInterface).IsAssignableFrom(XXType)

 

如何调用带有out参数的方法

这里以数值类型的TryParse函数为例

parameters[1]就是out返回的参数

 1 var tryParseFunc = xxxx;
 2 
 3             if(tryParseFunc != null)
 4             {
 5                 object[] parameters = new object[] { getValue.ToString(), null };
 6                 object result = tryParseFunc.Invoke(null, parameters);
 7                 bool blResult = (bool)result;
 8                 if (blResult)
 9                 {
10                     //parameters[1];
11                 }
12             }

 

如何获取可空类型的实际声明类型

Nullable<int>/int?在获取type时,会返回Nullable,而不是int。可以用下面的方法获取int类型

1      public static Type GetNullableDeclaringType(Type type)
2         {
3             if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
4             {
5                 return Nullable.GetUnderlyingType(type);
6             }
7 
8             return type;
9         }

 

动态实例化类型

CLR FileInfo类型为例

因为FileInfo需要一个string类型的参数,所以这里传递了参数。

1  var systemIOAssembly = Assembly.Load("System.IO");
2  var type = typeof(FileInfo);
3  var instance =  Activator.CreateInstance(type,"D:\\output.txt");

 

对于没有参数的类型,直接使用下面的代码即可

1 var instance =  Activator.CreateInstance(type);

 

动态调用成员函数

动态实例化类型后,我们可以调用实例的成员函数。

这里以FileInfo.Delete函数为例

首先通过GetMethod函数获取函数类型,然后再调用Invoke函数以调用函数。Invoke函数可以传递调用函数时需要的参数。

Invoke函数的返回值就是函数的返回值。

1 instance.GetType().GetMethod("Delete").Invoke(instance, null);

 

使用动态类型调用成员函数

也可以使用dynamic关键字,调用方法如下:

1  var systemIOAssembly = Assembly.Load("System.IO");
2  var type = typeof(FileInfo);
3  dynamic instance = Activator.CreateInstance(type, "D:\\output.txt");
4  instance.Delete();

这种方法看起来像是用强类型的方式调用一个方法,但是Visual Studio并不会提示,也不会在编译时进行检查。

如果函数名输入错误,则程序会报错。

对于dynamic类型,编译器在编译期间会忽略类型检查。编译器会假设dynamic类型的对象定义的操作都是有效的。

 

使用反射实例化泛型类型

1 Type d1 = typeof(List<>);
2 
3 
4 Type listStringType = d1.MakeGenericType(typeof(string));
5 
6 object instance = Activator.CreateInstance(listStringType);

 

使用反射挂钩委托

这里以WPF程序为例,我们使用反射,为窗口添加一个MouseDown事件处理程序。

首先我们创建一个WPF工程,并添加一个MyWindow窗口类型,同时在MainWindow上增加一个按钮,当按钮点击时,就创建MyWindow类型并显示。

 

MainWindow.xaml

 1 <Window x:Class="ReflectionHookupDelegate.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:ReflectionHookupDelegate"
 7         mc:Ignorable="d"
 8         Title="MainWindow" Height="450" Width="800">
 9     <Grid>
10         <Button Content="动态创建Window" HorizontalAlignment="Center" VerticalAlignment="Center" Click="Button_Click"></Button>
11     </Grid>
12 </Window>

 

挂钩步骤

1、加载程序集

这里因为是在同一个程序集下,所以我们使用Assembly.GetExecutingAssembly即可

1  var assembly = Assembly.GetExecutingAssembly();

 

2、获取类型,并创建实例

  Type myWindow = assembly.GetType("MyWindow");
  var myWindowInstance = Activator.CreateInstance(myWindow);

 

3、获取表示该事件的 EventInfo 对象,并使用 EventHandlerType 属性来获取用于处理事件的委托类型。

1 EventInfo mouseDown = myWindow.GetEvent("MouseDown");
2 Type tDelegate = mouseDown.EventHandlerType;

 

4、获取表示处理事件的方法的 MethodInfo 对象。 

MouseDown的事件处理程序委托声明如下:

1 public delegate void MouseButtonEventHandler(object sender, MouseButtonEventArgs e);

 

我们创建一个类 EventProcClass,并添加一个处理函数MyWindowMouseDownHandler

1     public class EventProcClass
2     {
3         public void MyWindowMouseDownHandler(object sender, MouseButtonEventArgs e)
4         {
5             MessageBox.Show("HelloWorld");
6         }
7     }

 

然后我们获取表示处理事件的方法的MethodInfo对象

1 var eventProcClassType = assembly.GetType("ReflectionHookupDelegate.EventProcClass");
2 MethodInfo myHandler = eventProcClassType.GetMethod("MyWindowMouseDownHandler", BindingFlags.Public | BindingFlags.Instance);

 

5、使用 CreateDelegate 方法创建委托的实例

1 //创建事件处理程序类型的实例
2 var evetProcInstance = Activator.CreateInstance(eventProcClassType);
3 
4 //创建委托
5 Delegate myEventProcDelegate = Delegate.CreateDelegate(tDelegate, evetProcInstance, myHandler);

 

6、获取 add 访问器方法,并调用该方法以将事件挂钩。 所有事件都有一个 add 访问器和一个 remove 访问器。

1 MethodInfo addHandler = mouseDown.GetAddMethod();
2 Object[] addHandlerArgs = { myEventProcDelegate };
3 addHandler.Invoke(myWindowInstance, addHandlerArgs);

 

7、测试事件(完整代码)

 1  private void Button_Click(object sender, RoutedEventArgs e)
 2  {
 3      //加载程序集
 4      var assembly = Assembly.GetExecutingAssembly();
 5 
 6      //创建窗口实例
 7      Type myWindow = assembly.GetType("ReflectionHookupDelegate.MyWindow");
 8      var myWindowInstance = Activator.CreateInstance(myWindow);
 9 
10      //获取事件委托类型
11      EventInfo mouseDown = myWindow.GetEvent("MouseDown");
12      Type tDelegate = mouseDown.EventHandlerType;
13 
14      //获取表示处理事件的方法的MethodInfo对象
15      var eventProcClassType = assembly.GetType("ReflectionHookupDelegate.EventProcClass");
16      MethodInfo myHandler = eventProcClassType.GetMethod("MyWindowMouseDownHandler", BindingFlags.Public | BindingFlags.Instance);
17 
18      //创建事件处理程序类型的实例
19      var evetProcInstance = Activator.CreateInstance(eventProcClassType);
20 
21      //创建委托
22      Delegate myEventProcDelegate = Delegate.CreateDelegate(tDelegate, evetProcInstance, myHandler);
23 
24      //挂钩事件处理程序
25      MethodInfo addHandler = mouseDown.GetAddMethod();
26      Object[] addHandlerArgs = { myEventProcDelegate };
27      addHandler.Invoke(myWindowInstance, addHandlerArgs);
28 
29      //测试显示窗口
30      myWindow.GetMethod("Show").Invoke(myWindowInstance, null);
31  }

此时我们在界面上点击 ,会弹框并显示"HelloWorld"

 

使用动态方法在运行时生成事件处理程序

在前面的例子中,我们添加了一个类EventProcClass,及一个成员函数MyWindowMouseDownHandler。这都是预先定义好的。

下面的教程演示如何动态生成MyWindowMouseDownHandler函数。

注意:动态方法需要具备一定的IL知识。

https://learn.microsoft.com/en-us/dotnet/standard/managed-code

https://en.wikipedia.org/wiki/Common_Intermediate_Language

 

实现步骤如下:

1、使用轻量动态方法和反射发出可在运行时生成事件处理程序方法。

     若要构造事件处理程序,需要委托的返回类型和参数类型。 可通过检查委托的 Invoke 方法来获取这些类型。

 1 private Type GetDelegateReturnType(Type d)
 2 {
 3     if (d.BaseType != typeof(MulticastDelegate))
 4         throw new ArgumentException("Not a delegate.", nameof(d));
 5 
 6     MethodInfo invoke = d.GetMethod("Invoke");
 7     if (invoke == null)
 8         throw new ArgumentException("Not a delegate.", nameof(d));
 9 
10     return invoke.ReturnType;
11 }
12 
13 private Type[] GetDelegateParameterTypes(Type d)
14 {
15     if (d.BaseType != typeof(MulticastDelegate))
16         throw new ArgumentException("Not a delegate.", nameof(d));
17 
18     //可通过检查委托的 Invoke 方法来获取参数类型
19     MethodInfo invoke = d.GetMethod("Invoke");
20     if (invoke == null)
21         throw new ArgumentException("Not a delegate.", nameof(d));
22 
23     //获取参数
24     ParameterInfo[] parameters = invoke.GetParameters();
25     Type[] typeParameters = new Type[parameters.Length];
26     for (int i = 0; i < parameters.Length; i++)
27     {
28         typeParameters[i] = parameters[i].ParameterType;
29     }
30     return typeParameters;
31 }

 

1 Type returnType = GetDelegateReturnType(tDelegate);
2 if (returnType != typeof(void))
3     throw new ArgumentException("Delegate has a return type.");
4 
5 DynamicMethod handler = new DynamicMethod("",null, GetDelegateParameterTypes(tDelegate),typeof(MainWindow));

 

2、生成方法体

此方法加载字符串、调用带有字符串的 MessageBox.Show 方法重载、从堆栈弹出返回值(因为处理程序没有返回类型)并返回这些值。

关于动态方法生成,可以参考下面的资料:https://learn.microsoft.com/zh-cn/dotnet/fundamentals/reflection/how-to-define-and-execute-dynamic-methods

 1 ILGenerator ilgen = handler.GetILGenerator();
 2 
 3 Type[] showParameters = { typeof(String) };
 4 MethodInfo simpleShow = typeof(MessageBox).GetMethod("Show", showParameters);
 5 
 6 ilgen.Emit(OpCodes.Ldstr,
 7     "This event handler was constructed at run time.");
 8 ilgen.Emit(OpCodes.Call, simpleShow);
 9 ilgen.Emit(OpCodes.Pop);
10 ilgen.Emit(OpCodes.Ret);

 

3、通过调用动态方法的 CreateDelegate 方法完成该动态方法

然后再使用使用 add 访问器向事件的调用列表添加委托。

1             //创建委托
2             Delegate dEmitted = handler.CreateDelegate(tDelegate);
3 
4 
5             //挂钩事件处理程序
6             MethodInfo addHandler = mouseDown.GetAddMethod();
7             Object[] addHandlerArgs = { dEmitted };
8             addHandler.Invoke(myWindowInstance, addHandlerArgs);

 

4、测试

1             //测试显示窗口
2             myWindow.GetMethod("Show").Invoke(myWindowInstance, null);

 

示例代码

 

推荐阅读:

反射

https://docs.microsoft.com/zh-cn/dotnet/framework/reflection-and-codedom/reflection

动态方法

https://learn.microsoft.com/zh-cn/dotnet/fundamentals/reflection/how-to-define-and-execute-dynamic-methods

posted @ 2020-12-11 18:29  zhaotianff  阅读(388)  评论(0)    收藏  举报