C#反射教程(5)
在目录下新建一个程序文件,并命名为LateBinding.cs,编写代码如代码7.12所示。
代码7.12 晚期绑定:LateBinding.cs
//导入相应的命名空间
using System.Reflection;
using System.IO;
class LateBinding
{
static void Main(string[] args)
{
Console.Write("/n【1】请输入传递给OldClass类Method静态方法的参数:");
string inputA = Console.ReadLine();
Console.Write("/n【2】请输入传递给NewClass类Method方法的参数:");
string inputB = Console.ReadLine();
Console.Write("/n【3】请输入传递给MyClass类Method方法的参数:");
string inputC = Console.ReadLine();
Console.WriteLine("/n/t=======以下是不同程
序集的不同方法调用结果=======/n");
try
{
//将用户输入的3组值传递给以下3个方法
LoadOldClass(inputA);
LoadNewClass(inputB);
LoadMyClass(inputC);
}
//捕获文件未找到异常
catch (FileNotFoundException e)
{
//输出异常信息
Console.WriteLine("异常信息:{0}", e.Message);
}
//捕获一般异常
catch (Exception e)
{
//输出异常信息
Console.WriteLine("异常信息:{0}", e.Message);
}
}
//定义LoadOldClass方法,接收一个string类型参数
//该类用于加载OldClass程序集,并调用该程序集中OldClass类的Method静态方法
static void LoadOldClass(string input)
{
//调用Assembly的Load方法,载入OldClass程序集
Assembly am = Assembly.Load("OldClass");
//获取OldClass类的Type对象
Type OldTp = am.GetType("OldClass", false, false);
//定义仅一个参数的数组OldList,子项初始化为input参数
string[] OldList = new string[1] { input };
//调用OldTp的InvokeMember方法,传递相应的参数
//将方法返回结果转换为string类型并赋值给txt变量
string txt = (string)OldTp.InvokeMember(
"Method",
BindingFlags.InvokeMethod,
null,
null,
OldList
);
//输出txt变量
Console.WriteLine(txt);
}
//定义LoadNewClass方法,接收一个string类型参数
//该类用于加载NewClass程序集,并调用该程序集中NewClass类对象的Method实例方法
static void LoadNewClass(string input)
{
//创建当前应用程序域的指定程序集中指定类型的实例
object obj = AppDomain.CurrentDomain.
CreateInstanceAndUnwrap("NewClass", "NewClass");
//调用obj的GetType方法,获取Type对象
Type Newtp = obj.GetType();
//定义仅一个参数的数组NewList,子项初始化为input参数
string[] NewList = new string[1] { input };
//调用Newtp的InvokeMember方法,传递相应的参数
//将方法返回结果转换为string类型并赋值给txt变量
string txt = (string)Newtp.InvokeMember(
"Method",
BindingFlags.InvokeMethod,
null,
obj,
NewList
);
//输出txt变量
Console.WriteLine(txt);
}
//定义LoadMyClass方法,接收一个string类型参数
//该类用于加载MyClass程序集,并调用该程序集中MyClass类对象的Method实例方法
static void LoadMyClass(string input)
{
//定义仅一个参数的数组MyList,子项初始化为input参数
string[] MyList = new string[1] { input };
//调用Assembly的Load方法,载入MyClass程序集
Assembly MyAm = Assembly.Load("MyClass");
//获取MyClass类的Type对象
Type MyTp = MyAm.GetType("MyClass",false,false);
//调用Activator类的CreateInstance方法
//该方法可创建参数类型的实例
object MyObj = Activator.CreateInstance(MyTp);
//搜索MyTp的参数指定方法,并返回给mi变量
MethodInfo mi = MyTp.GetMethod("Method");
//根据参数调用mi的方法,并将返回结果赋值给txt变量
string txt = (string)mi.Invoke(MyObj, MyList);
//输出txt变量
Console.WriteLine(txt);
mi = MyTp.GetMethod("MethodTxt");
//根据参数调用mi的方法,并将返回结果赋值给txt变量
txt = (string)mi.Invoke(MyObj, null);
//输出txt变量
Console.WriteLine(txt);
}
}
在命令行下将OldClass.cs、NewClass.cs和MyClass.cs编译为dll程序集,如图7.14所示。
图7.14 编译外部程序集
在命令行下编译LateBinding.cs,执行LateBinding程序,运行结果如图7.15所示。
图7.15 晚期绑定
本程序以多种情况展示了晚期绑定的编写方法,主程序的3大部分分别封装在LoadOldClass()、LoadNewClass()和 LoadMyClass()静态方法中。考虑到外部程序集有可能不存在,主程序编写了"文件未找到"的异常捕捉。LoadOldClass()和 LoadNewClass()方法的晚期绑定通过InvokeMember()方法调用对象的成员,而LoadMyClass()方法则直接通过如下代码完成方法的调用。
string txt = (string)mi.Invoke(MyObj, MyList);
首先第1行代码从Type类型中获取参数所指定名称的方法,并返回MethodInfo类型的对象mi,然后调用mi的Invoke()方法,即可执行Method()方法。Invoke()方法的第1个参数调用Method()方法的对象,第2个参数传递给方法的参数数组,如果Method()方法没有参数,可以使用null。
解析
由于没有在当前程序集的清单中列出动态加载的外部程序集,所以编译时编译器并不知道该程序集是否存在。如果在程序中创建该程序集指定类型的实例,并调用其成员,这就是晚期绑定技术。晚期绑定可以使用System.Activator类,如以下代码所示:
using System.Reflection;
Assembly am = Assembly.Load("外部程序集名称");
Type tp = am .GetType("MyClass",false,false);
object obj = Activator.CreateInstance(tp);
以上代码创建了MyClass类的对象引用obj,如果需要调用其成员,不能直接用点符号调用,因为该对象为object类型,而不是指定类型。调用其成员可以使用该类型Type对象的InvokeMember()方法,并根据多个参数确定所调用的成员信息。例如, obj对象的Method()方法接收string类型的一个参数,并且返回类型为string。如果调用obj对象的Method()方法,方法代码如下所示:
string txt = (string)OldTp.InvokeMember(
"Method",//成员名称
BindingFlags.InvokeMethod,//绑定标记,
null,//绑定对象,
obj,//所调用对象
Param//所传递参数数组
);
以上代码中的InvokeMember()方法的返回值即为Method()方法的返回值,由于InvokeMember()方法返回值是 object类型,所以需要强制转换为string类型再赋值给txt。InvokeMember()方法的第1个参数为字符串类型,代表所调用成员的名称;第2个参数为绑定标记,代表成员类型,该值需要访问BindingFlags类,例如成员方法BindingFlags.InvokeMethod;第3个参数为绑定对象,一般使用null,代表使用默认绑定器,即System.Type.DefaultBinder类对象;第4个参数为所调用的对象 obj,如果调用静态方法,该参数为null。第4个参数传递给方法参数数组,例如代码中的Param。
创建外部程序集指定类型的实例也可调用当前应用程序域的CreateInstanceAndUnwrap()方法,如以下代码所示:
object obj = AppDomain.CurrentDomain.
CreateInstanceAndUnwrap("程序集名称", "类型名称");
面试例题10:如何通过晚期绑定读写属性和字段成员?
考点:InvokeMember()方法读写属性和字段成员以及Invoke()方法读写属性和字段成员。
出现频率:★★★
解答
晚期绑定读写属性和字段成员均可以使用晚期绑定对象的InvokeMember()方法实现,也可以使用所指定类型的Type对象反射实现。本题在 LateBindingOther.cs代码中,主程序读写Human.dll程序集中Person类的Name属性和_age字段。在目录下新建一个程序文件,并命名为Person.cs,编写代码如代码7.13所示。
代码7.13 外部Person类:Person.cs
class Person
{
//定义两个字段,其中_name字段由Name属性读写
string _name;
//定义public的int类型的_age字段
public int _age = 0;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
在目录下新建一个程序文件,并命名为LateBindingOther.cs,编写代码如代码7.14所示。
代码7.14 晚期绑定读写属性和字段成员:LateBindingOther.cs
//导入相应的命名空间
using System.Reflection;
using System.IO;
class LateBindingOther
{
static void Main(string[] args)
{
try
{
//接收用户输入的两组值,并传递给ClassOP方法
Console.Write("/n【1】请输入需写入Person的属性值:");
string inputA = Console.ReadLine();
Console.Write("/n【2】请输入需写入Person的字段值:");
int inputB = System.Convert.ToInt32(Console.ReadLine());
Console.WriteLine("/n/t=======以下是属
性和字段被写入后读出的结果=======/n");
ClassOP(inputA, inputB);
}
//捕获输入格式异常
catch (FormatException e)
{
//输出异常信息
Console.WriteLine("异常信息:{0}", e.Message);
}
//捕获文件未找到异常
catch (FileNotFoundException e)
{
//输出异常信息
Console.WriteLine("异常信息:{0}", e.Message);
}
//捕获一般异常
catch (Exception e)
{
//输出异常信息
Console.WriteLine("异常信息:{0}", e.Message);
}
}
static void ClassOP(string a, int b)
{
//调用Assembly的Load方法,载入Human程序集
Assembly am = Assembly.Load("Human");
//获取Person类的Type对象
Type tp = am.GetType("Person", false, false);
//调用Activator类的CreateInstance方法
//该方法可创建参数类型的实例
object obj = Activator.CreateInstance(tp);
//写入Name属性值
tp.InvokeMember("Name", BindingFlags.SetProperty,
null, obj, new string[] { a });
//读取Name属性值
string name = (string)tp.InvokeMember("Name",
BindingFlags.GetProperty, null, obj, null);
Console.WriteLine("Person类的Name属性值为:【{0}】", name);
//写入_age字段值
tp.InvokeMember("_age", BindingFlags.SetField,
null, obj, new object[] { b });
//读取_age字段值
int age = (int)tp.InvokeMember("_age",
BindingFlags.GetField, null, obj, null);
Console.WriteLine("Person类的_age字段值为:【{0}】", age);
}
}
在命令行下将Person.cs编译为Human.dll程序集,编译LateBindingOther.cs,执行LateBindingOther程序。程序将提示"【1】请输入需写入Person的属性值:"和"【2】请输入需写入Person的字段值:",分别输入"比尔"和"50",运行结果如图7.16所示。
图7.16 晚期绑定读写属性和字段成员
本程序采用InvokeMember()方法成功地读写了Person类的Name属性和_age字段,也可以使用另一种方法(Invoke()方法)完成相同的功能。
解析
类似于晚期绑定调用方法,读写属性和字段成员可以用相同的方法实现。假设指定类型的Type对象为tp,晚期绑定所创建该类型对象为obj,使用InvokeMember()方法读写string类型的属性成员如以下代码所示:
tp.InvokeMember("属性名称", BindingFlags.SetProperty, null, obj,
属性值数组(object类型));
//读取属性值到txt变量
string txt = (string)tp.InvokeMember("属性名称",
BindingFlags.GetProperty, null, obj, null);
以上代码通过编写不同的绑定标记,访问BindingFlags类的成员,
设定obj对象成员的操作类型及方式。如果不用InvokeMember()方法,
也可选用Invoke()方法解决,如以下代码所示:
//获取指定属性的PropertyInfo类型对象p
PropertyInfo p = tp.GetProperty("属性名称");
//写入属性
p.SetValue(obj,属性值数组,null);
//读取属性值到txt变量
string txt = (string)p.GetValue(obj,null);
该方法更为简单,也比较直观,SetValue()方法和GetValue()方法最后一个参数为索引化属性值的索引值,如果不是索引化属性值则取null。