在目录下新建一个程序文件,并命名为LateBinding.cs,编写代码如代码7.12所示。
代码7.12 晚期绑定:LateBinding.cs
+展开
-C#
using System;
//导入相应的命名空间
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()方法则直接通过如下代码完成方法的调用。
+展开
-C#
MethodInfo mi = MyTp.GetMethod(
"Method");
string txt = (
string)mi.Invoke(MyObj, MyList);
首先第1行代码从Type类型中获取参数所指定名称的方法,并返回MethodInfo类型的对象mi,然后调用mi的Invoke()方法,即可执行Method()方法。Invoke()方法的第1个参数调用Method()方法的对象,第2个参数传递给方法的参数数组,如果Method()方法没有参数,可以使用null。
解析
由于没有在当前程序集的清单中列出动态加载的外部程序集,所以编译时编译器并不知道该程序集是否存在。如果在程序中创建该程序集指定类型的实例,并调用其成员,这就是晚期绑定技术。晚期绑定可以使用System.Activator类,如以下代码所示:
+展开
-C#
using System;
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()方法,方法代码如下所示:
+展开
-C#
string[] Param =
new
string[1] {
"参数值" };
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()方法,如以下代码所示:
+展开
-C#
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
+展开
-C#
using System;
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
+展开
-C#
using System;
//导入相应的命名空间
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类型的属性成员如以下代码所示:
+展开
-C#
//写入属性
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。