如何调用方法?
本示例阐释如何通过反射调用各种方法。由于所调用方法的名称存储在字符串中,因此该机制提供在运行时(而不是在设计时)指定要调用的方法的功能,提供了使您的用户可以控制调用哪个特定方法的余地。尽管本演示集中于调用方法,如果需要您还可以设置和获取属性和字段。有关本主题的另一个实例示教,请参阅
如何使用数学函数主题下的示例。
在许多代码方案中,在执行任务以前您知道要实现的任务。因此,您可以指定需要调用的方法以及需要传递给它们的参数。但是,还有一些情况下您可能希望根据特定方案或用户操作动态调用方法。该功能可通过 Reflection 命名空间使用,方法是使用 Type 对象上的 InvokeMember 方法。
您还可以进行其他操作,如获取或设置指定属性的值。这些操作可通过 BindingFlags 枚举使用。InvokeMethod 的第二个参数是您指定的 BindingFlags 操作的组合。例如,如果想调用某个类上的静态方法,可以在 BindingFlags 和 InvokeMethod BindingFlag 中包括该静态元素。下面的示例展示如何调用名为 SayHello 的假想方法,其中 SayHello 是静态方法。
// calling a static method, receiving no arguments
// don't forget that we are using object in the reflection namespace...
using System;
using System.Reflection;
public class Invoke {
public static void Main (String [] cmdargs) {
// Declare a type object, used to call our InvokeMember method...
Type t = typeof (TestClass);
// BindingFlags has three bitor'ed elements. Default indicates
// that default binding rules should be applied.
t.InvokeMember ("SayHello",
BindingFlags.Default | BindingFlags.InvokeMethod
| BindingFlags.Static, null,
null, new object [] {});
}
}
' calling a static method, receiving no arguments
' don't forget that we are using object in the reflection namespace...
Imports System
Imports System.Reflection
Public Class Invoke
Public Shared Sub Main ()
' Declare a type object, used to call our InvokeMember method...
Dim t As Type = GetType (TestClass)
' BindingFlags has three bitor'ed elements. Default indicates that
' default binding rules should be applied.
t.InvokeMember ("SayHello", _
BindingFlags.Default BitOr BindingFlags.InvokeMethod _
BitOr BindingFlags.Static, nothing, _
nothing, new object () {})
End Sub
End Class
|
| C#
|
VB
|
|
快速查看一下传递给 Invoke 方法的其余参数。传递的第一个空参数请求使用默认联编程序绑定正在调用的方法。当调用默认联编程序时,请包含默认的 BindingFlags。第三个参数可以不为空,您可以指定一个 Binder 对象,它定义一组属性并启用绑定,这可能涉及选择重载方法或强制参数类型。第二个空参数是您在其上调用所选方法的对象。最后,传递由成员接收的参数对象数组。在本例中,SayHello 方法不接收任何参数,因此传递一个空数组。
下面的情况略有不同。调用名为 ComputeSum 的另一个静态方法,但是在此情况下,此方法需要两个参数。因此,用这些参数填充一个对象数组,并将它们作为最后一个参数传递到 InvokeMember 中。
// Calling a static method, which needs arguments
object [] args = new object [] {100.09, 184.45};
// we know that this particular method returns a value, being the computed sum,
// so we create a variable to hold the return
// note the datatype of the return is object, the only datatype InvokeMethod returns...
object result;
// invoke the method. Note the change in the last parameter: the array we populated...
result = t.InvokeMember ("ComputeSum", BindingFlags.Default | _
BindingFlags.InvokeMethod | BindingFlags.Static,
null, null, args);
// write the results to the user's console...
Console.WriteLine ("{0} + {1} = {2}", args[0], args[1], result);
' Calling a static method, which needs arguments
Dim args as object ()
args = new object () {100.09, 184.45}
' we know that this particular method returns a value, being the computed sum,
' so we create a variable to hold the return
' note the datatype of the return is object, the only datatype InvokeMethod returns...
Dim result As object
' invoke the method. Note the change in the last parameter: the array we populated...
result = t.InvokeMember ("ComputeSum", BindingFlags.Default BitOr _
BindingFlags.InvokeMethod BitOr BindingFlags.Static, _
nothing, nothing, args)
' write the results to the user's console...
Console.WriteLine ("{0} + {1} = {2}", args(0), args(1), result)
|
| C#
|
VB
|
|
在前两个示例中,已调用了静态方法。还可以调用实例方法。若要这样做,将您要在其上调用方法的类型的对象作为第三个参数传递。本示例还展示为了使用 InvokeMember,您不必有实际的 Type 对象。在此情况下,通常将希望使用所拥有的类实例来调用 GetType,如下面的示例所示。注意由于未调用静态方法,所以 BindingFlags 已更改。
// Calling an instance method
// we need an object reference to invoke an instance member
TestClass c = new TestClass ();
// use the instance of our class to call GetType
// we no longer include the Static element in BindingFlags for our |
// the fourth parameter is no longer null: we instead pass an instance
// of the object we wish to invoke our method on
c.GetType().InvokeMember ("AddUp", BindingFlags.Default | BindingFlags.InvokeMethod,
null, c, new object [] {});
c.GetType().InvokeMember ("AddUp", BindingFlags.Default | BindingFlags.InvokeMethod,
null, c, new object [] {});
' Calling an instance method
' we need an object reference to invoke an instance member
Dim c as TestClass
c = new TestClass ()
' use the instance of our class to call GetType
' we no longer include the Static element in BindingFlags for our bitor
' the fourth parameter is no longer null: we instead pass an instance
' of the object we wish to invoke our method on
c.GetType().InvokeMember ("AddUp", BindingFlags.Default BitOr BindingFlags.InvokeMethod, _
nothing, c, new object () {})
c.GetType().InvokeMember ("AddUp", BindingFlags.Default BitOr BindingFlags.InvokeMethod, _
nothing, c, new object () {})
|
| C#
|
VB
|
|
有时不想调用方法,而需要调用其他成员,如属性或字段。若要实现它,只需更改 BindingFlags 组合(而不是 InvokeMethod)以包含适当元素即可。下面的示例展示获取和设置字段值。所讨论字段不是静态字段,因此需要创建一个对象实例来请求该字段。设置字段值时,需要将所设置的值作为对象数组参数的唯一元素传递。获取值时,需要将 InvokeMember 方法的返回类型分配给一个对象。
// Setting a field. Assume we are using the same Type and Class declared in the
// previous examples (t and c). The field we are setting is the Name field
// note the BindingFlags argument now includes SetField rather thanInvokeMember
// Further, this is an instance field, so we pass the instance of our class
t.InvokeMember ("Name", BindingFlags.Default | BindingFlags.SetField,
null, c, new object [] {"NewName"});
// similar usage...
result = t.InvokeMember ("Name", BindingFlags.Default | BindingFlags.GetField,
null, c, new object [] {});
Console.WriteLine ("Name == {0}", result);
' Setting a field. Assume we are using the same Type and Class declared in the
' previous examples (t and c). The field we are setting is the Name field
' note the BindingFlags argument now includes SetField rather thanInvokeMember
' Further, this is an instance field, so we pass the instance of our class
t.InvokeMember ("Name", BindingFlags.Default BitOr BindingFlags.SetField, _
nothing, c, new object () {"NewName"})
' similar usage...
result = t.InvokeMember ("Name", BindingFlags.Default BitOr BindingFlags.GetField, _
nothing, c, new object () {})
Console.WriteLine ("Name == {0}", result)
|
| C#
|
VB
|
|
还可以获取和设置属性,但在本示例中,假定所设置属性是一个具有多个元素的数组或集合。若要指定特定元素的设置,您需要指定索引。若要设置属性,请分配 BindingFlags.SetProperty。若要指定属性的集合索引或数组索引,请将要设置元素的索引值放在对象数组的第一个元素中,然后将要设置的值作为第二个元素。若要取回该属性,请将索引作为对象数组中的唯一元素传递,指定 BindingFlags.GetProperty。
// Set an indexed property value
int index = 3;
// specify BindingFlags.SetProperty, and because this is an instance property,
// pass the object to call the property on (c). In the object array, make two elements,
// the first being the index, and the second being the value to set
t.InvokeMember ("Item", BindingFlags.Default |BindingFlags.SetProperty,
null, c, new object [] {index, "NewValue"});
// Get an indexed property value
// specify BindingFlags.GetProperty, and because this is an instance property,
// pass the object to call the property on (c). In the object array, specify the index only
result = t.InvokeMember ("Item", BindingFlags.Default |BindingFlags.GetProperty,
null, c, new object [] {index});
Console.WriteLine ("Item[{0}] == {1}", index, result);
' Set an indexed property value
Dim index As Int32 = 3
' specify BindingFlags.SetProperty, and because this is an instance property,
' pass the object to call the property on (c). In the object array, make two elements,
' the first being the index, and the second being the value to set
t.InvokeMember ("Item", BindingFlags.Default BitOr BindingFlags.SetProperty, _
nothing, c, new object () {index, "NewValue"})
' Get an indexed property value
' specify BindingFlags.GetProperty, and because this is an instance property,
' pass the object to call the property on (c). In the object array, specify the index only
result = t.InvokeMember ("Item", BindingFlags.Default BitOr BindingFlags.GetProperty, _
nothing, c, new object () {index})
Console.WriteLine ("Item[{0}] == {1}", index, result)
|
| C#
|
VB
|
|
还可以使用命名参数,在此情况下需要使用 InvokeMember 方法的另一个重载版本。像迄今一直进行的那样创建对象参数的数组,并创建所传递参数的名称的字符串数组。您要使用的重载方法接受参数名列表作为最后一个参数,并接受要设置的值的列表作为第五个参数。在本演示中,所有其他参数都可以为空(当然前两个除外)。
// Calling a method using named arguments
// the argument array, and the parameter name array. Obviously, you will need
// to determine the names of the parameters in advance
object[] argValues = new object [] {"Mouse", "Micky"};
String [] argNames = new String [] {"lastName", "firstName"};
// the first five parameters for this overloaded method are the same as the
// the five parameters we have used to this point. The final parameter needs to be
// set to the names of the parameters
t.InvokeMember ("PrintName", BindingFlags.Default | BindingFlags.InvokeMethod,
null, null, argValues, null, null, argNames);
' Calling a method using named arguments
' the argument array, and the parameter name array. Obviously, you will need
' to determine the names of the parameters in advance
object[] argValues = new object [] {"Mouse", "Micky"};
String [] argNames = new String [] {"lastName", "firstName"};
' the first five parameters for this overloaded method are the same as the
' the five parameters we have used to this point. The final parameter needs to be
' set to the names of the parameters
t.InvokeMember ("PrintName", BindingFlags.Default BitOr BindingFlags.InvokeMethod, _
nothing, nothing, argValues, nothing, nothing, argNames)
|
| C#
|
VB
|
|
下一个示例展示如何调用类上的默认成员。确保在其上进行调用的类指定有默认成员。然后在 InvokeMember 方法中,不要指定要调用成员的名称,如本示例所示。
// our class with it's default member specified, using the defaultmemeber attribute
[DefaultMemberAttribute ("PrintTime")]
public class TestClass2 {
public void PrintTime () {
Console.WriteLine (DateTime.Now);
}
}
// the client code that uses the above class...
Type t3 = typeof (TestClass2);
t3.InvokeMember ("", BindingFlags.Default |BindingFlags.InvokeMethod,
null, new TestClass2(), new object [] {});
' our class with it's default member specified, using the defaultmemeber attribute
public class TestClass2
public Sub PrintTime ()
Console.WriteLine (DateTime.Now)
End Sub
End Class
' the client code that uses the above class...
Dim t3 As Type
t3 = GetType (TestClass2)
t3.InvokeMember ("", BindingFlags.Default BitOr BindingFlags.InvokeMethod, _
nothing, new TestClass2(), new object () {})
|
| C#
|
VB
|
|
最后一个示例使用略有不同的过程调用方法。不直接使用 Type 对象,而是直接创建一个单独的 MethodInfo 对象来表示将调用的方法。然后调用 MethodInfo 对象上的 Invoke 方法,传递需要在其上调用方法的对象的实例(在要调用实例方法的情况下,但是,如果方法是静态的,则为空)。像以前一样,需要参数的对象数组。如果需要,该特定示例允许您通过引用传递参数。
// Invoking a ByRef member
MethodInfo m = t.GetMethod("Swap");
args = new object[2];
args[0] = 1;
args[1] = 2;
m.Invoke(new TestClass(),args);
Console.WriteLine ("{0}, {1}", args[0], args[1]);
' Invoking a ByRef member
Dim m as MethodInfo =
m = t.GetMethod("Swap")
args = new object() {CObj(1), CObj(2)}
m.Invoke(new TestClass(),args)
Console.WriteLine ("{0}, {1}", args(0), args(1))
|
| C#
|
VB
|
|
如何列出某类型的所有成员
本示例使您可以列出给定数据类型的成员。列出类型成员的功能是快速发现哪些元素可用的很好方式。它是在系统中进行报告以及帮助开发用户文档的重要工具。使用
Reflection 命名空间,您可以控制希望显示给用户的成员类型以及其他信息(如特定方法的可见性)。还可以获取类中所有成员的信息,或仅指定某些子集(如方法或字段)。
您可能想知道为何获取特定类型的信息很重要。毕竟,这就是帮助系统和帮助文档的用途,不是吗?下面的示例可以帮助您创建用户文档,或用于帮助动态调用方法或设置属性。
需要执行以下几个步骤。首先,需要获取用户希望使用的类型(以字符串的形式)。确定了要使用的类型后,需要分配一个对象来表示该类型。这将进行两项工作:创建后面步骤可以使用的对象,还确保指定类型存在并且可被系统找到。下面的示例向 System.String 类型分配一个对象。注意,尽管此处示例中通过“控制台”(Console) 对象向用户提供反馈,实际示例却将反馈发送给一个 ASP.NET 标签对象。但解释相同。
// don't forget your using statements at the top of your code...
Using System;
Using System.Reflection;
// class declaration, and method declaration...
// remember that this string is case-sensitive, so be careful
Type t = Type.GetType("System.String");
// check to see if we have a valid value. If our object is null, the type does not exist...
if (t == null) {
// Don't assume that it is a SYSTEM datatype...
Console.WriteLine("Please ensure you specify only valid types in the type field.");
Console.WriteLine("REMEMBER: The Case matters (Byte is not the same as byte).");
return; // don't continue processing
}
' don't forget your imports statements at the top of your code...
Imports System
Imports System.Reflection
' class declaration, and method declaration...
' remember that this string is case-sensitive, so be careful
Dim t As Type = Type.GetType("System.String")
' check to see if we have a valid value. If our object is null, the type does not exist...
If t Is Nothing Then
' Don't assume that it is a SYSTEM datatype...
Console.WriteLine("Please ensure you specify only valid types in the type field.")
Console.WriteLine("REMEMBER: The Case matters (Byte is not the same as byte).")
Exit Sub ' don't continue processing
End If
|
| C#
|
VB
|
|
有了有效的类型对象以后,下一个问题是希望为类型检索什么类型的成员?是需要方法、静态方法还是实例字段?在 Reflection 命名空间中,有一组 Info 对象,每个对象表示您系统的一组不同成员。例如,有一个 MethodInfo 对象可表示有关某方法的信息。还有一个一般 MemberInfo 对象,它表示给定类中可以存在的所有成员。
使用该信息,您可设置以下数组来查看刚刚创建的类型,并弄清类型中有哪种类型的信息。下面示例中的“位”运算符(|符号,或 Visual Basic 中的 BitOr)请求满足指定约束的类型的所有信息。该示例展示如何获取所有字段和所有方法。
// declare and populate the arrays to hold the information...
FieldInfo [] fi = t.GetFields (BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public); // fields
MethodInfo [] mi = t.GetMethods (BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public); // methods
' declare and populate the arrays to hold the information...
Dim fi() As FieldInfo = t.GetFields(BindingFlags.Static BitOr _
BindingFlags.NonPublic BitOr BindingFlags.Public) ' fields
Dim mi() As MethodInfo = t.GetMethods(BindingFlags.Static BitOr _
BindingFlags.NonPublic BitOr BindingFlags.Public) ' methods
|
| C#
|
VB
|
|
下一步是迭代通过每个数组,并在屏幕上列出数组中的元素(显然,您将实际处理这些元素或标识数组中的某个特定元素)。有多种方法可以进行该操作,但在该示例中使用 Foreach(Visual Basic 中为 For Each)语句。
// iterate through all the method members
foreach (MethodInfo m in mi) {
Console.WriteLine(m);
}
// iterate through all the field members
foreach (FieldInfo f in fi) {
Console.WriteLine(f);
}
// etc.... for each array type
Dim m As MethodInfo
Dim f As FieldInfo
' iterate through all the method members...
For Each m In mi
Console.WriteLine(m)
Next m
' iterate through all the field members
For Each f In fi
Console.WriteLine(f)
Next f
' etc.... for each array type
|
| C#
|
VB
|
|
前面的代码工作良好,但请注意两条 Foreach(Visual Basic 中为 For Each)语句多么类似。将所有这些都写出来非常费力而且杂乱(并且如果以后更改代码,可能需要大量维护)。可以通过返回到 MemberInfo 对象来规避这一点。MemberInfo 对象包括所有可能的信息集(方法、字段和接口等等)。这可以对我们有所帮助,因为我们可以将以前的多个 Foreach 语句写成一个语句,传入数组以进行分析。
// call the routine below, passing the relevant array we made in the previous step
PrintMembers( mi ); // the method information
PrintMembers( fi ); // the field information
void PrintMembers (MemberInfo [] ms ) {
// MemberInfo is the generic info object. This can be any of the other info objects.
foreach (MemberInfo m in ms) {
Console.WriteLine(m);
}
}
' call the routine below, passing the relevant array we made in the previous step
PrintMembers( mi ) ' the method information
PrintMembers( fi ) ' the field information
Sub PrintMembers (ms() As MemberInfo)
Dim m As MemberInfo
' MemberInfo is the generic info object. This can be any of the other info objects.
For Each m in ms
Console.WriteLine(m)
Next m
End Sub
|
| C#
|
VB
|
|
运行该示例时您将注意到,同时会发生其他一些事情。不必硬编码 System.String 对象,可以指定要获取有关哪个类的信息。它还使您可以控制是要显示静态信息还是实例信息。
如何获取程序集内的类型
本示例阐释如何检索给定程序集的所有类型。若要浏览程序集的类型,首先需要标识想操作的程序集。在使某对象引用了感兴趣的程序集后,可以在该程序集上调用
GetTypes 方法,它返回包含该程序集内所有类型的一个数组。您可以使用控制逻辑标识该数组中的更具体类型,并使用迭代逻辑分析您的数组,在需要时向用户返回类型信息。检索类型信息的功能对确定可用于给定任务的其他类型很有用,或对标识可为您提供所需功能的现有元素很有用。
从特定程序集检索类型时要学习的首要内容是如何标识程序集。本"快速入门"展示检索程序集的两种方法。第一种方法是标识要在程序集内查找的特定对象,并向程序集请求该对象的模块(记住模块是类型和代码的逻辑分组,如 .dll 或 .exe)。第二种方法是使用 Assembly 类的 LoadFrom 方法,为指定模块(如 myapp.exe)加载特定程序集。
// don't forget your using statements
using System;
using System.Reflection;
// ...
// Getting an Assembly, method 1. Get the mscorlib assembly
// Note that other types such as String, or Int32 would have worked just as well,
// since they reside in the same assembly
Assembly a = typeof(Object).Module.Assembly;
// Getting an Assembly, method 2. Load a particular assembly, using a reference to a
// module that is within that assembly. Note that this requires a compiled module for
// the reference, and when running in an aspx page, will require a fully qualifed path
// to the file, to ensure it is correctly identified
Assembly b = Assembly.LoadFrom ("GetTypes.exe");
// note that either of the above methods is viable, depending on the information
// you have. Since we know the name of the file which houses all of the base system
// objects, we could do the following to replace the first example, just as effectively
// (the absolute path may change on your machine)
// Assembly a = Assembly.LoadFrom
// ("c:/winserv/microsoft.net/framework/v1.0.2230/mscorlib.dll");
' don't forget your using statements
Imports System
Imports System.Reflection
' ...
' Getting an Assembly, method 1. Get the mscorlib assembly
' Note that other types such as String, or Int32 would have worked just as well,
' since they reside in the same assembly
Dim a As reflection.Assembly = GetType(Object).Module.Assembly
' Getting an Assembly, method 2. Load a particular assembly, using a reference to a
' module that is within that assembly. Note that this requires a compiled module for
' the reference, and when running in an aspx page, will require a fully qualifed path
' to the file, to ensure it is correctly identified
Dim b As reflection.Assembly = reflection.Assembly.LoadFrom ("GetTypes.exe")
' note that either of the above methods is viable, depending on the information
' you have. Since we know the name of the file which houses all of the base system
' objects, we could do the following to replace the first example, just as effectively
' (the absolute path may change on your machine)
' Dim a As reflection.Assembly = reflection.Assembly.LoadFrom _
' ("c:/winserv/microsoft.net/framework/v1.0.2230/mscorlib.dll")
|
| C#
|
VB
|
|
标识了程序集后,现在可以继续检索类型,将 GetTypes 方法的返回值分配给 Type 对象的数组。现在便可以操作这些类型了。在下面的示例中,您将获取核心运行时库的类型,并分别计算该程序集内不同类型样式的数目(如果需要有关 Foreach (For Each) 语句的更多信息,请参阅如何迭代通过集合主题下的内容)。尽管还可以计算其他成员(如类和枚举)的数目,但在该示例中,将只展示如何计算接口数目。
//Get all the types in the assembly identified in the previous example
Type [] types = a.GetTypes ();
int numInterfaces = 0;
foreach (Type t in types) {
//the following line uses a set of methods which identify what
//kind of type we are currently querying
if (t.IsInterface) {
// only print out the names of the Interfaces
Console.WriteLine (t.Name + "");
numInterfaces++;
}
}
// write out the totals
Console.WriteLine("Out of {0} types in the {1} library:",
types.Length, typeof(Object).Module.ToString());
Console.WriteLine ("{0} are interfaces (listed)", types.Length, numInterfaces);
' Get all the types in the assembly identified in the previous example
Dim types() As Type = a.GetTypes ()
Dim numInterfaces As Integer = 0
Dim t As Type
For Each t in types
' the following line uses a set of methods which identify what
' kind of type we are currently querying
If t.IsInterface Then
' only print out the names of the Interfaces
Console.WriteLine (t.Name + "'")
numInterfaces = numInterfaces + 1
End If
Next t
' write out the totals
Console.WriteLine("Out of {0} types in the {1} library:", _
types.Length, GetType(Object).Module.ToString())
Console.WriteLine ("{0} are interfaces (listed)", types.Length, numInterfaces)
|
| C#
|
VB
|
|
还可以使用第一个示例中标识的第二种方法检索给定程序集的类型。在下面的示例中,您将注意到它并未使用相同的基结构来依次通过类型,因为您不需要跟踪类型的不同种类。当查看小型程序集时这很适合,如当前运行的应用程序(获取 MSCorLib 的所有类型的列表将得到一个非常大的列表)。
// Get all the types in the assembly identified in the previous example (this assembly)
Type [] types2 = b.GetTypes ();
Console.WriteLine ("Get all the types from the assembly: '{0}'", b.GetName());
foreach (Type t in types2)
{
Console.WriteLine (t.FullName);
}
' Get all the types in the assembly identified in the previous example (this assembly)
Dim types2() As Type = b.GetTypes ()
Console.WriteLine ("Get all the types from the assembly: '{0}'", b.GetName())
For Each t in types2
Console.WriteLine(t.FullName) ' not many types, so we can print them all
Next t
|
| C#
|
VB
|
|