在前面几篇当中,先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,以及如何利用反射来获取特性对象。
在本文中,将学习如何使用反射动态地创建一个对象。
动态创建对象
新建一个类Calculator作为示范,其代码如下:
public class Calculator
{
private int x;
private int y;
public Calculator()
{
x = 0;
y = 0;
Console.WriteLine("Calculator() invoked");
}
public Calculator(int x, int y)
{
this.x = x;
this.y = y;
Console.WriteLine("Calculator(int x, int y) invoked");
}
}
创建对象通常有两种方式
1. Assembly的CreateInstance方法
2. Activator的CreateInstance方法
下面是它们的方法签名,有多个重载。
Assembly的CreateInstance方法:
public object CreateInstance(string typeName);
public object CreateInstance(string typeName, bool ignoreCase);
public virtual object CreateInstance(string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes);
Activator的CreateInstance方法:
public static object CreateInstance(Type type);
public static object CreateInstance(Type type, bool nonPublic);
public static object CreateInstance(Type type, params object[] args);
public static object CreateInstance(Type type, object[] args, object[] activationAttributes);
public static object CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes);
public static T CreateInstance<T>();
public static ObjectHandle CreateInstance(string assemblyName, string typeName);
public static ObjectHandle CreateInstance(string assemblyName, string typeName, object[] activationAttributes);
public static ObjectHandle CreateInstanceFrom(AppDomain domain, string assemblyFile, string typeName);
对于上面方法有些返回object,有一些返回ObjectHandle,实际上ObjectHandle类只是一个对原类型进行了一个包装以便进行封送。一个ObjectHandle对象,必须执行一次Unwrap()才能返回Object类型。
使用无参数构造函数创建对象
1. Assembly的CreateInstance方法
Assembly asm = Assembly.GetExecutingAssembly();
Object obj = asm.CreateInstance("ReflectionDemo.Calculator", true);
// 输出:Calculator() invoked
2. Activator的CreateInstance方法
ObjectHandle handler = Activator.CreateInstance("ReflectionDemo", "ReflectionDemo.Calculator");
Object obj2 = handler.Unwrap();
使用有参数构造函数创建对象
Assembly asm = Assembly.GetExecutingAssembly();
Object[] parameters = new Object[2]; // 定义构造函数需要的参数
parameters[0] = 3;
parameters[1] = 5;
Object obj3 = asm.CreateInstance("ReflectionDemo.Calculator" //类名称
, true //类名称忽略大小写
, BindingFlags.Default
, null
, parameters //构造器参数
, null
, null);
看一下CreateInstance需要提供的参数:
1)前两个参数在上一小节已经说明过了;
2)BindingFlags在前面也出现过,它用于限定对类型成员的搜索。在这里指定Default,意思是不使用BingdingFlags的策略;
3)接下来的参数是Binder,它封装了CreateInstance绑定对象的规则,几乎永远都会传递null进去,使用内置的DefaultBinder;
4)接下来是一个Object[]数组类型,它包含传递进去的构造函数参数,有参数的构造函数将会使用这些参数。Object[]数组中元素的顺序,与构造函数参数列表中的顺序需要保持一致。
5)接下来的参数是一个CultureInfo类型,它包含了关于语言和文化的信息(简单理解就是对于ToString("c"),什么时候显示“¥”,什么时候显示“$”)。
动态调用方法
接下来再看一下如何动态地调用方法。本小节讨论的调用不是将上面动态创建好的对象由Object类型强制转换成Calculator类型再进行方法调用,这样就和普通的方法调用没有区别了,而是利用反射,基于字符串来调用方法。之所以称为“动态”,是因为字符串可以由各种途径获得,比如是从客户端传递进来的一个参数。
先为Calculator添加一个实例方法,一个静态方法作为本节的示例:
public int Add()
{
int total = 0;
total = x + y;
Console.WriteLine("Invoke Instance Method: ");
Console.WriteLine(String.Format("[Add]: {0} plus {1} equals to {2}", x, y, total));
return total;
}
public static void Add(int x, int y)
{
int total = x + y;
Console.WriteLine("Invoke Static Method: ");
Console.WriteLine(String.Format("[Add]: {0} plus {1} equals to {2}", x, y, total));
}
调用方法的方式一般有两种:
1. 通过Type对象的GetMethond()方法,获取想要调用的方法对象,也就是MethodInfo对象,然后在该对象上调用Invoke方法。根据方法签名,可能还需要传递参数。
2. 在类型的Type对象上调用InvokeMember()方法,传递要在其上调用方法的对象(对本例而言,就是Calculator类型实例),并指定BindingFlags为InvokeMethod。根据方法签名,可能还需要传递参数。
需要说明的是,使用InvokeMember()不限于调用对象的方法,也可以用于获取对象的字段、属性,实现方式都是类似的,本节只说明最常见的调用方法。
通过Type对象的GetMethond()方法,得到MethodInfo对象,使用MethodInfo对象进行方法调用。
调用一个实例方法,代码示例:
Type t = typeof(Calculator);
Calculator c = new Calculator(3, 5);
MethodInfo mi = t.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public);
mi.Invoke(c, null);
输出为:
Calculator(int x, int y) invoked
Invoke Instance Method:
[Add]: 3 plus 5 equals to 8
先使用GetMethod()方法获取了一个方法对象MethodInfo,指定BindingFlags为Instance和Public,因为Calculator类中有两个方法都命名为"Add",所以在这里指定搜索条件是必需的。
接着使用Invoke()调用了Add方法,第一个参数是Calculator的类型实例,表明在该实例上调用方法;第二个参数为null,说明方法不需要提供参数。
调用一个静态方法,代码示例:
Type t = typeof(Calculator);
object[] parameters = new object[] { 6, 9 };
MethodInfo mi = t.GetMethod("Add", BindingFlags.Static | BindingFlags.Public);
mi.Invoke(null, parameters);
输出为:
Invoke Static Method:
[Add]: 6 plus 9 equals to 15
可以看到与调用实例方法大同小异,在GetMethod()方法中,指定为搜索BindingFlags.Static,而不是BindingFlags.Public,因为现在要调用的是静态的Add()方法。
在Invoke()方法中,第一个参数不需要再传递Calculator的类型实例,因为静态方法不是属于某个具体实例的。
使用InvokeMember调用方法
Type t = typeof(Calculator);
Calculator c = new Calculator(3, 5);
int result = (int)t.InvokeMember("Add", BindingFlags.InvokeMethod, null, c, null);
Console.WriteLine(String.Format("The result is {0}", result));
输出的结果为:
Calculator(int x, int y) invoked
Invoke Instance Method:
[Add]: 3 plus 5 equals to 8
The result is 8
在InvokeMember()方法中,
第一个参数是想要调用的方法名称;
第二个参数是调用方法(InvokeMember()方法的功能非常强大,不光可以调用方法,还可以获取/设置属性、字段等。);
第三个参数是Binder,null说明使用默认的System.Type.DefaultBinder;
第四个参数是在哪个类型实例上进行调用,对于本例来说,是c,这是因为调用的是一个实例方法;
最后一个参数是数组类型,表示的是方法所接受的参数。
再看一下对于静态方法应该如何调用:
object[] parameters = { 6, 9};
Type t = typeof(Calculator);
t.InvokeMember("Add", BindingFlags.InvokeMethod, null, t, parameters);
输出的结果为:
Invoke Static Method:
[Add]: 6 plus 9 equals to 15
和调用实例方法进行一下对比:
首先,第四个参数传递的是typeof(Calculator),不再是一个Calculator实例类型,这是因为调用的是一个静态方法,它不是基于某个具体的类型实例,而是基于类型本身;
其次,因为Add()静态方法需要提供两个参数,所以以数组的形式将这两个参数进行了传递。
通过上面的例子可以看出:使用反射可以在最大程度上实现多态。举个例子,可以在页面上放置一个下拉框,然后指定它的列表项的Value为某个类型的方法名称,使用Value的值来调用该类型的方法。如果不使用反射,则只能写一些if-else语句,先判断选择的项的值,然后再根据值决定调用哪个方法。当使用这种方式时,编译器在代码运行之前(或者说用户选择了某个列表项之前),完全不知道类型的哪个方法将被调用,这也就是常说的迟绑定(Late Binding)。
本文回顾:
动态创建对象
动态调用方法