分享
 
 
 

在.NET运行时了解类型信息(2)

王朝c#·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

反射类所使用的设计模式

System.Reflection 类中最常用的方法都使用统一的模式。Module、Type 和 MemberInfo 类的成员使用下表中所示的设计模式。

成员签名

说明

MyInstance[] FindXxx(filter, filterCriteria)

查找并返回经过筛选的类型列表,或者在当前类型没有实现任何匹配筛选器的类型的情况下返回空数组。

例如:Type.FindInterfaces

MyInstance GetXxx(<parameters>)

返回由 <parameters> 唯一指定的类型。如果不存在这样的类型,成员将返回 null(在 Microsoft Visual Basic .NET 中为 Nothing)。请注意,<parameters> 唯一地指定一个实例。

例如:Type.GetInterface

MyInstance[] GetXxxs()

返回所有公共类型。如果不存在公共类型,成员将返回空数组。

例如:Type.GetFields

MyInstance[] GetXxxs(<parameters>)

返回由 <parameters> 指定的所有类型。如果不存在这样的类型,成员将返回空数组。请注意,<parameters> 并不一定指定唯一的实例。

另一个常用的设计模式是使用委托。它们通常在反射中用来支持对返回对象数组的方法的结果集进行筛选。

反射的安全注意事项

如果提供对非公共信息的访问,将带来安全风险。如果允许代码了解某类型的非公共信息,那么该代码就有可能会访问您需要保密的代码、数据和其他信息。因此,.NET 框架安全性强制实施了一些规则,以确定可以使用哪种程度的反射来了解类型信息和访问类型。根据所执行的操作,可能会需要用于序列化的 ReflectionPermission 或 SecurityPermission。

在未经许可的情况下,所有代码都可以使用反射来执行以下任务:

获取有关公共类型及其公共成员的信息。

了解代码所在的模块和程序集。

枚举公共类型。

枚举与使用反射的代码位于同一程序集中的非公共类型。

枚举程序集和模块。

调用公共成员。

对调用代码基类的族访问成员进行调用。

对调用代码程序集的程序集访问成员进行调用。

若要了解有关非公共成员的信息,调用方必须具有 ReflectionPermission,此权限表示可以获取类型信息。如果不具有此权限,代码将无法使用反射通过 Type、Assembly 和 Module 上的 Get 方法来获取有关非公共成员(即使属于它自己的类)的信息。

要使用反射来调用无法按照通用类型系统可访问性规则访问的方法或访问这样的字段,必须向代码赋予成员访问的 ReflectionPermission。

注意 建议使安全策略拒绝向源自 Internet 的代码授予 ReflectionPermission。

序列化的 SecurityPermission 提供了获取和设置可序列化类型的任何非瞬态数据字段(即不仅存在于内存中的成员)的能力,而不论是否可以访问这些字段。此权限使代码能够了解并更改实例的私有状态。(除了授予正确的权限之外,还必须在元数据中将类型标记为可序列化。)

链接请求检查

如果方法或委托对某一 P 权限具有 LinkDemand,运行库将对该方法或委托的调用方执行链接请求检查,以验证已经向调用方授予 P 权限。在了解类型信息和进行调用时,都会执行此链接请求检查。

应避免编写采用 MethodInfo 参数的公共 API,尤其是对于高度信任的代码。否则,调用权限可能会很容易被恶意代码绕过。例如,设想在高度信任的代码中有一个采用 MethodInfo 参数的公共 API。假设此公共 API 对所提供的参数间接地调用 MethodInfo.Invoke。如果公共 API 没有执行必要的权限检查,由于安全性系统断定调用方受到高度信任,对 Invoke 方法的调用将始终会成功。即使恶意代码无权直接调用该方法,它仍然能够通过调用公共 API 来间接地调用该方法。

动态加载和使用类型

反射提供了由语言编译器(例如 Microsoft Visual Basic .NET 和 JScript)用来实现隐式晚期绑定的基础结构。绑定是查找与唯一指定的类型相对应的声明(即实现)的过程。由于此过程在运行时而不是在编译时发生,所以称作晚期绑定。Visual Basic .NET 允许您在代码中使用隐式的晚期绑定;Visual Basic 编译器将调用一个帮助器方法,该方法使用反射来获取对象类型。传递给帮助器方法的参数有助于在运行时调用正确的方法。这些参数包括对其调用方法的实例(对象)、被调用方法的名称(字符串)和传递给被调用方法的参数(对象数组)。

在以下代码示例中,Visual Basic 编译器使用反射隐式地对其类型在编译时未知的对象调用方法。HelloWorld 类具有一个 PrintHello 方法,它输出与传递给 PrintHello 方法的某些文本串联的“Hello World”。在该示例中调用的 PrintHello 方法实际上是 Type.InvokeMember;Visual Basic 代码允许按照对象 (helloObj) 的类型在编译时已知(早期绑定)而不是在运行时已知(晚期绑定)的方式来调用 PrintHello 方法。

自定义绑定

除了由编译器隐式地用来进行晚期绑定之外,反射还可以在代码中显式地用来完成晚期绑定。

公共语言运行库支持多种编程语言,但这些语言的绑定规则各不相同。在早期绑定的情况下,代码生成器可以完全控制此绑定。但是,当通过反射进行晚期绑定时,必须用自定义绑定来控制绑定。Binder 类提供了对成员选择和调用的自定义控制。

利用自定义绑定,您可以在运行时加载程序集,获取有关该程序集中类型的信息,然后对该类型调用方法或访问该类型的字段或属性。如果您在编译时(例如当对象类型依赖于用户输入时)不知道对象的类型,就可以使用这种方法。以下代码示例显示在 HelloWorld.dll 程序集中使用反射动态调用的方法(首先在 Visual Basic .NET 中,然后在 C# 中)。

[C#]

// This class is deployed as an assembly consisting of one DLL,

// called HelloWorld.dll.

using System;

public class HelloWorld {

// Constant Hello World string.

private const String m_helloWorld = "Hello World";

// Default public constructor.

public HelloWorld() {

}

// Print "Hello World" plus the passed text.

public void PrintHello(String txt) {

// Output to the Console.

Console.WriteLine(m_helloWorld + " " + txt);

}

}

// Illustrates reflection's late binding functionality.

// Calls the PrintHello method on a dynamically loaded

// and created instance of the HelloWorld class.

using System;

using System.Reflection;

public class CSharpLateHello {

public static void Main() {

// Load the assembly to use.

Assembly assem = Assembly.Load("HelloWorld");

// Get the type to use from the assembly.

Type helloType = assem.GetType("HelloWorld");

// Get the method to call from the type.

MethodInfo printMethod = helloType.GetMethod("PrintHello");

// Create an instance of the HelloWorld class.

Object obj = Activator.CreateInstance(helloType);

// Create the args array.

Object[] args = new Object[1];

// Set the arguments.

args[0] = "From CSharp Late Bound";

// Invoke the PrintHello method.

printMethod.Invoke(obj, args);

}

}

InvokeMember 和 CreateInstance

使用 Type.InvokeMember 可调用类型的成员。各个类(如 System.Activator 和 System.Reflection.Assembly)的 CreateInstance 方法是特殊形式的 InvokeMember,它们可新建特定类型的实例。Binder 类用于在这些方法中进行重载决策和参数强制。

以下代码示例显示参数强制(类型强制)和成员选择三种可能的组合。在第 1 种情况中,不需要任何参数强制或成员选择。在第 2 种情况中,只需要成员选择。在第 3 种情况中,只需要参数强制。

[C#]

public class CustomBinderDriver

{

public static void Main (string[] arguments)

{

Type t = typeof (CustomBinderDriver);

CustomBinder binder = new CustomBinder();

BindingFlags flags = BindingFlags.InvokeMethod|BindingFlags.Instance|

BindingFlags.Public|BindingFlags.Static;

//Case 1. Neither argument coercion nor member selection is needed.

args = new Object[] {};

t.InvokeMember ("PrintBob", flags, binder, null, args);

//Case 2. Only member selection is needed.

args = new Object[] {42};

t.InvokeMember ("PrintValue", flags, binder, null, args);

//Case 3. Only argument coercion is needed.

args = new Object[] {"5.5"};

t.InvokeMember ("PrintNumber", flags, binder, null, args);

}

public static void PrintBob ()

{

Console.WriteLine ("PrintBob");

}

public static void PrintValue (long value)

{

Console.WriteLine ("PrintValue ({0})", value);

}

public static void PrintValue (String value)

{

Console.WriteLine ("PrintValue\"{0}\")", value);

}

public static void PrintNumber (double value)

{

Console.WriteLine ("PrintNumber ({0})", value);

}

}

当多个成员具有相同的名称时,将需要重载决策。Binder.BindToMethod 和 Binder.BindToField 方法用于解析与单个成员的绑定。Binder.BindToMethod 还通过 get 和 set 属性访问器提供了属性解析。

BindToMethod 返回要调用的 MethodBase,如果无法进行这样的调用,则返回 null。虽然 MethodBase 返回值通常是 match 参数中所包含的值之一,但它并不必如此。

当存在 ByRef 参数时,调用方可能需要取回这些参数。因此,如果 BindToMethod 已经操作参数数组,Binder 会允许将参数数组映射回它的初始形式。为了实现这一目的,必须向调用方保证参数的顺序不会改变。当按名称传递参数时,联编程序将重新排列参数数组,这就是调用方所见的参数。

可用成员集包括在类型和任何基类型中定义的成员。如果指定 BindingFlags.NonPublic,将返回该成员集中具有任何可访问性的成员。如果未指定 BindingFlags.NonPublic,联编程序就必须强制可访问性规则。当指定 Public 或 NonPublic 绑定标志后,还必须指定 Instance 或 Static 绑定标志,否则不会返回任何成员。

如果只有一个成员具有给定名称,则不必进行回调,而在该方法上进行绑定。代码示例的第 1 种情况说明了这一点:只有一个 PrintBob 方法可用,因此不需要进行回调。

如果可用集中有多个成员,所有这些方法都将传递给 BindToMethod,它将选择正确的方法并将其返回。在代码示例的第 2 种情况下,有两个名为 PrintValue 的方法。对 BindToMethod 的调用将选择正确的方法。

ChangeType 执行参数强制(类型强制),以便将实参转换为选定方法的形参的类型。即使类型精确匹配,仍会为每个参数调用 ChangeType。

在代码示例的第 3 种情况下,类型为 String 值为“5.5”的实参传递给 Double 类型的形参的方法。要使调用成功,必须将字符串值“5.5”转换为 double 值。ChangeType 会执行此转换。

ChangeType 仅执行无损或扩大转换,如下表所示。

源类型

目标类型

任何类型

它的基类型

任何类型

它所实现的接口

Char

UInt16、UInt32、Int32、UInt64、Int64、Single、Double

Byte

Char、UInt16、Int16、UInt32、Int32、UInt64、Int64、Single、Double

SByte

Int16、Int32、Int64、Single、Double

UInt16

UInt32、Int32、UInt64、Int64、Single、Double

Int16

Int32、Int64、Single、Double

UInt32

UInt64、Int64、Single、Double

Int32

Int64、Single、Double

UInt64

Single、Double

Int64

Single、Double

Single

Double

非引用类型

引用类型

Type 类具有 Get 方法,这些方法使用 Binder 类型的参数来解析对特定成员的引用。Type.GetConstructor、Type.GetMethod 和 Type.GetProperty 通过提供成员的签名信息来搜索当前类型的特定成员。Binder.SelectMethod 和 Binder.SelectProperty 则被回调来选择相应方法的给定签名信息。

访问默认成员

任何类型都可以具有默认成员,即在未给定任何成员名称时调用的成员。以下代码示例调用 Class1 的默认成员,并将它返回的值赋给 i。

默认成员用 System.Reflection.DefaultMemberAttribute 进行标记。以下代码示例显示如何通过检索默认成员的自定义属性来检索默认成员。

[C#]

Type t = typeof(DefaultMemberAttribute);

DefaultMemberAttribute defMem = (DefaultMemberAttribute)Attribute.GetCustomAttribute(Assembly.GetAssembly(t), t);

MemberInfo[] memInfo = t.GetMember(defMem.MemberName);

使用 Type.GetDefaultMembers 可能会简单一些,并且会生成完全相同的结果。但是,如果在类型上定义了多个默认成员,GetDefaultMembers 就会引发 InvalidOperationException。以下代码示例显示 GetDefaultMembers 的语法。

[C#]

MemberInfo[] memInfo = t.GetDefaultMembers();

通过以 String.Empty ("") 为成员名称来调用 Type.InvokeMember,就可以调用默认成员。InvokeMember 将从类型中检索 DefaultMemberAttribute,然后调用它。

访问默认参数值

某些语言(如 C++ 托管扩展和 Microsoft Visual Basic .NET)支持将默认值赋给参数。例如,以下代码示例是一个合法的 Visual Basic .NET 声明,此声明将默认值赋给两个参数。

您可以使用参数属性来分配默认的参数值。

通过确切指定哪些参数是默认值或略去尾部的默认参数,可以声明参数的默认值。例如,以下所有代码示例都是对 MyMethod 的有效调用。

[C#]

MyMethod (10, 55.3, 12);

MyMethod (10, 1.3); // c == 1

MyMethod (11); // b == 1.2, c == 1

要使用反射检索参数的默认值,请获取该参数的 ParameterInfo 对象,然后使用 ParameterInfo.DefaultValue 属性检索默认值。如果不存在默认值,该属性将返回 Value.DBNull。

以下代码示例向控制台显示 MyMethod 的默认值。

[C#]

MethodInfo m = t.GetMethod ("MyMethod");

ParameterInfo[] ps = m.GetParameters();

for (int i = 0; i < ps.Length; i++) {

Console.WriteLine("Default Value == {0}", ps[i].DefaultValue);

}

要调用包含具有默认值的参数的方法,请使用 Type.Missing 作为 InvokeMember 方法的参数值。这样,晚期绑定服务就能够为指定的参数值使用默认值。如果为不带默认值的参数传递 Type.Missing,则将引发 ArgumentException。有一点务必要注意,并非所有编译器的绑定机制都会遵守 Type.Missing 的这些规则。有些联编程序可能不支持此功能,或者可能以不同的方式来处理 Type.Missing。当使用 Type.Missing 时,默认值不必是结尾的参数值。

C# 语言不支持默认参数。

以下 Visual Basic .NET 代码示例显示如何调用具有默认参数的方法。

[Visual Basic]

Option Strict Off

Imports System

Imports System.Reflection

Public Class OptionalArg

Public Sub MyMethod (a As Integer, Optional b As Double = 1.2, Optional c As Integer=1)

Console.WriteLine("a = " & a & " b = " & b & " c = " & c)

End Sub

End Class

Module Module1

Sub Main()

Dim o As New OptionalArg

Dim t As Type

t = GetType(OptionalArg)

Dim Param As Object()= {10, 20, 30}

t.InvokeMember("MyMethod", _

BindingFlags.Public Or _

BindingFlags.Instance Or _

BindingFlags.InvokeMethod Or _

BindingFlags.OptionalParamBinding, _

Nothing, _

o, _

New Object() {10, 55.3, 12})

t.InvokeMember("MyMethod", _

BindingFlags.Public Or _

BindingFlags.Instance Or _

BindingFlags.InvokeMethod Or _

BindingFlags.OptionalParamBinding, _

Nothing, _

o, _

New Object() {10, 1.3, Type.Missing})

t.InvokeMember("MyMethod", _

BindingFlags.Public Or _

BindingFlags.Instance Or _

BindingFlags.InvokeMethod Or _

BindingFlags.OptionalParamBinding, _

Nothing, _

o, _

New Object() {10, Type.Missing, Type.Missing})

End Sub

End Module

当使用上述方法时,即使调用方未指定任何值,仍会考虑尾部的默认参数。这是调用具有默认参数的方法时最常用的方式。

如果是使用 MethodBase.Invoke 来调用方法,则需要显式指定哪些参数是默认值,指定的方法是为所有没有值的参数传递一个包含 Type.Missing 的对象数组。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有