第三节:反射的性能

反射是相当强大的一个机制,它允许在运行时发现并使用编译时还不了解的类型及其成员。但是,它也有下面两个缺点:

1 、反射会造成编译时无法保证类型的安全性,由于反射要严重依赖于字符串,所以会丧失编译时类型安全。例如:假如执行Type.GetType(“Jef”);要求通过反射在一个程序中查找一个名为”Jef”的类型,但程序集包含的实际是”Jeff”类型,代码会通过编译,但是在运行时会出错,因为作为实参传递的类型名称被错误地拼写。

2、反射速度慢。使用反射时,类型以及成员的名称在编译时未知;要使用字符串名称来标识每个类型以及成员,以便在运行时发现他们。也就是说,使用System.Reflection命名空间中的类型扫描程序集的元数据时,反射要不断的执行字符串搜索。通常,字符串搜索执行时不区分大小写的比较,这回近异步影响速度。

使用反射调用一个成员时,也会对性能产生影响。用反射调用一个方法时,首先必须将实参打包成一个数据,在内部,反射必须将这些实参解包到线程栈上。此外,在调用方法前,CLR必须检查实参具有正确的数据类型。最后,CLR必须确保调用者有正确的安全权限来访问被调用的成员。

基于上诉所有原因,最好避免利用反射来访问字段或者调用方法和属性。如果要写一个应用程序来动态发现和构造类型实例,应采取以下两种技术之一。

1、         让类型从一个已知的基类型派生。在运行时,构造派生类的一个实例,将对他的引用放到基类型的变量中,再调用基类型定义的虚方法。

2、         让类型实现一个编译时已知的接口。在运行时,构造一个类型的实例,将对他的引用放到接口类型的变量中,再调用接口定义的方法。

在这两种技术中,我个人更喜欢接口技术而非基类技术,因为基类技术不允许开发人员选择在一个特定情况下工作的最好的基类,不过,在需要版本控制的情形下,基类的技术显的更合适一些,因为随时都能向基类添加一个成员,派生类会直接继承他。相反,要向接口添加一个成员,实现该接口的所有类型都得修改他们的代码并重新编译。

在使用这两种技术时,强烈建议在接口或基类型自己的程序集中定义他们,有助于缓解版本控制问题。

一、发现程序集中定义的类型

反射经常判断程序集中定义了哪些类型。FCL中提供了好多方面的信息。到目前为止,最常见的方法时Assembly的GetExportedTypes.下面加载一个程序集,并显示其中定义的所有公开导出的类型。

  private static void LoadAssemAndShowPublicTypes(string assemid)
        {
            Assembly a = Assembly.Load(assemid);
            foreach (Type t in a.GetExportedTypes())
            {
                Console.WriteLine(t.FullName);
            }
        }

  

二、类型对象的准确含义

注意,上述代码遍历System.Type对象构成的一个数组。System.Type类型时执行类型和对象操作的起点。System.Type时一个从System.Reflection.MemberInfo派生的抽象基类,FCL中定义了几个从System.Type派生的基类,包括System.RuntimeType,System.ReflectionOnlyType,System.Reflection.TypeDelegator以及System.Reflection.Emit命名空间中的一些类型。

所有这些类型中,System.RuntimeType是最有趣的一个。这个类型时FCL内部使用的一些类型。所以在FCL文档中找不到,一个类型在一个AppDomain中首次访问时,CLR会构造一个RuntimeType的一个实例,并初始化这个RuntimeType对象的字段。

我们知道,System.Object定义了一个公共非虚实例方法GetType。调用这个方法时,CLR会判断指定对象的类型,并返回对它的RuntimeType对象的一个引用。由于在一个AppDomain中,每个类型只有一个RuntimeType对象,所以可以使用相等和不相等操作符来判断两个对象是不是属于同一个类型。

除了调用Object的GetType方法,FCL还提供了获得Type对象的其他几种方式。

1、         System.Type类型提供了静态方法GetType的几个重载版本,每个方法的所有版本都接受一个String参数。这个字符串必须制定类型的全名(包括它的命名空间)。注意,不允许制定编译器支持的基元类型。这些类型对CLR来说没有任何意义。如果传递字符串只是类型全名,方法将检索调用程序集,看他是否定义了制定名称的类型。如果是,就返回对一个恰当RuntimeType对象的引用。

如果调用程序集没有定义制定的类型,就检查MSCorLib.dll定义的类型,如果还是没有找到匹配名称的一个类型,就返回null或抛出System.TypeLoadException。

可想GetType传递一个限定了程序集的类型字符串,比如“System.Int32,mscorlib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=…..”GetType会在制定的程序集中查找类型。

2、         System.Type类型提供了一个静态方法ReflectionOnlyGetType。该方法和提到的GetType方法的执行相似,只是类型回家再到仅反射的上下文,不能执行。

3、         System.Type类型提供了一下实例方法:GetNestedType与GetNestedTypes。

4、 System.Reflection.Module类型提供了以下实例方法:GetType ,GetTypes, FindTypes

注意:我们知道,构建传给反射方法的字符串时,要使用类型名称和限定了程序集的类型名称,微软为这些名称定了巴克斯-诺尔范式语法。使用反射时,了解这些语法会对你有不少好处,尤其是处理嵌套类型,泛型类型,泛型方法,引用参数或者数组时。

许多编程语言还提供一个操作符,允许根据编译时已知的类型名称来获得一个Type对象。如果有可能,应该用这个操作符来获得对一个Type对象的引用,而不是使用上诉列表中的任何方法,因为操作符通常能生成更快的代码。C#的这个操作符称为typeof,通常可用该操作符将晚期绑定的信息和早期绑(编译时已知)定的信息进行比较。一下演示了一个例子:

private static void SomeMethod(Object o)

{

if (o.GetType() == typeof(FileInfo)) { }

if (o.GetType() == typeof(DirectoryInfo)) { }

}

注意:上述代码中的第一个if语句检查变量o是否引用了FileInfo类型的一个对象;它不检查o是否引用从FileInfo类型派生的一个对象。换言之,上面代码检测的是精确匹配,而非兼容匹配。(使用C#的is/as操作符时,测试的就是兼容匹配)

获得对一个Type对象的引用后,就可查询类型的许多属性,更进一步了解该类型,大多数属性,比如IsPublic , IsSealed ,IsAbstract ,IsClass, IsValueType等,即指明了与类型关联的标志。另一些属性,比如:Assembly,AssemblyQualifiedName,FullName,Module等,则返回定义该类型的那个程序集或模块的名称,或返回类型全名,还可查询BaseType属性来获取类型的基类型。除此之外还有许多方法能提供类型的更多信息。

 

三、构造类型的实例

拥有对一个Type对象的派生类型之后,就可以构造该类型的一个实例。为此,FCL提供了一下几种机制。

1. System.Activator的CreateInstance方法      System.Activator类提供了静态  CreateInstance方法的几个重载版本。调用该方法时,可以传递一个Type对象引用,也可以传递标识了想要创建的类型的一个String。直接获取一个类型对象的几个版本要简单一些,你要为类型的构造器传递一组实参,方法返回是对新对象的一个引用。

使用字符串来指定所需类型的几个版本则稍微复杂一些。首先,必须指定另一个字符串来标识定义了类型的那个程序集。其次,如果正确配置了远程访问选项,这些方法还允许远程构造对象。第三,这些这些版本返回不是对想对象的一个引用,而是一个System.Runtime.Remoting.ObjectHandle对象(派生自System.MarshalByRefObject)。

ObjectHandle类型允许将一个AppDomain中创建的对象传给其他AppDomain,期间不强迫对象具体化。要具体化这个对象,请调用ObjectHandle的Unwrap方法。在一个AppDomain中调用该方法时,它会将定义了要具体化的类型的程序集加载到这个AppDomain中,如果对象按引用封送,会创建代理类型和对象。如果对象按值封送,对象的副本会被反序列化。

2. System.Activator的CreateInstanceFrom方法  Activator类还提供了一组静态CreateInstanceFrom方法。这些方法与CreateInstance方法行为相似。只是必须通过字符串来指定类型以及程序集。程序集要用Assembly的LoadFrom加载到调用AppDomain中。由于这些方法都不接受一个Type参数,所以返回的都是一个ObjectHandle对象引用,必须调用ObjectHandle的Unwrap方法进行具体化。

3. System.AppDomain的方法   AppDomain类型提供了4个用于构造类型实例的实例方法(每一个都有几个重载版本):  CreateInstance , CreateInstanceAndUnwrap,CreateInstanceFrom,CreateInstanceFromAndUnwrap。这些方法的行为和Activator类的方法相似,只是他们都是实例方法,允许制定在哪个AppDomain中构造对象。另外,带Unwrap后缀还可以简化我们的一些操作,因为不必在执行一次额外的方法调用。

4.Sytem.Type的InvokeMember实例方法  可以使用一个Type对象的引用来调用InvokeMember方法。该方法会查找与传递的实参匹配的一个构造器,并构造类型。类型总是在调用AppDomain中创建,返回的是对新对象的一个引用。

5. System.Reflection.ConstructorInfo   的Invoke实例方法  使用一个Type对象引用,可以绑定到一个特定的构造器,并获取对构造器ConstructorInfo   对象的一个引用。然后可以利用这个ConstructorInfo   对象的引用来调用它的Invoke方法。类型总是在AppDomain中创建,返回的是对新对象的一个引用。

利用前面列出的机制,可以除了数组(System.Array派生类)和委托(System.MulticastDelegate派生类)之外的所有类型创建一个对象。为了创建一个数组,应该调用Array的静态CreateInstance方法。所有版本的CreateInstance方法获取的第一个参数都是对数组元素Type的一个引用。CreateInstance的其他参数允许制定数组维数和上下限的各种组合。为了创建一个委托,应该调用Delegate的静态CreateDelegate方法。所有版本CreateDelegate方法的第一个参数都是对委托实例Type的一个引用。其他参数允许指定要在委托中包装的一个对象的实例方法(或者一个类型的静态方法)。

为了构造一个泛型类型的实例,首先要获取对开发类型的一个引用,然后调用Type的公共的实例方法MakeGenericType,向它传递一个数组(其中包含了要作为类型实参使用的类型),然后,获取返回Type对象,吧它传给上面列出的某个方法。

    internal sealed class Dictionary<TKey, TValue>
    {

    }

static void Main(string[] args)
        {
            Type openType = typeof(Dictionary<,>);

            Type closeType = openType.MakeGenericType(typeof(String), typeof(Int32));

            Object o = Activator.CreateInstance(closeType);

            Console.WriteLine(o.GetType());
        }

  

时间: 2024-11-01 18:43:19

第三节:反射的性能的相关文章

[C#.NET] C#中使用反射的性能分析

最近在研究一个可配置系统的框架,在代码中大量使用了反射的方法,虽然借鉴到其他的语言,如java中反射性能都比较差,但是想到c#既然是一种强类型的语言,对于AppDomain中的类的调用应该性能不会差很多.   今天在mvp站点上看到有人说反射的性能很差,要避免使用,就写了一个简单的例子测试了一下   测试类如下:   namespace ReflectionTest.Test   {    public class CTester    {    public CTester()    {   

再看ExpressionTree,Emit,反射创建对象性能对比

[前言] 前几日心血来潮想研究着做一个Spring框架,自然地就涉及到了Ioc容器对象创建的问题,研究怎么高性能地创建一个对象.第一联想到了Emit,兴致冲冲写了个Emit创建对象的工厂.在做性能测试的时候,发现居然比反射Activator.CreateInstance方法创建对象毫无优势可言.继而又写了个Expression Tree的对象工厂,发现和Emit不相上下,比起系统反射方法仍然无优势可言. 第一时间查看了园内大神们的研究,例如: Leven 的 探究.net对象的创建,质疑<再谈A

java反射机制性能优化

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; public class DynamicServer { private static Logger log = Logger.getLogger(DynamicServ

反射机制,反射的性能,如何优化?

反射机制的定义: 是在运行状态中,对于任意的一个类,都能够知道这个类的所有属性和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制. 反射的作用: 1.动态地创建类的实例,将类绑定到现有的对象中,或从现有的对象中获取类型. 2.应用程序需要在运行时从某个特定的程序集中载入一个特定的类.

单例模式防反射及性能(二)

单例模式的目的是创建一个对象,但是反射的方式,或者使用反序列的方式,就会对这种目的造成威胁,那么我们先来看看如何使用反射,如何使用反序列化,创建构造函数私有化的对象,以及我们如何防止反序列化创建对象. 1.补充:如何选用单例模式 (1)占用资源少,不需要延迟加载的,一般使用的是枚举和饿汉式,但是枚举比饿汉式安全. (2)占用资源大,需要延迟加载,一般使用静态内部类和懒汉式,静态内部类好于懒汉式,因为他更加的懒汉式.线程安全.调用的效率高. 2.使用反射破解的时候,一般是不包括枚举的,所以他的安全

java反射的性能问题

java反射效率到底如何,花了点时间,做了一个简单的测试.供大家参考. 测试背景: 1. 测试简单Bean(int,Integer,String)的set方法2. loop 1亿次3. 测试代码尽可能避免对象的创建,复发方法的调用,仅仅测试set方法的耗时 测试结果: 场景 本机测试结果(XP,双核,2G) 服务器测试结果(Linux,XEN虚拟机,8核,5.5G) 方法直接调用 235MS 190MS JDK Method调用 29188MS 4633MS JDK Method调用(稍作优化)

[转] 利用表达式树构建委托改善反射性能

最近搞一个系统时由于在比较关键地方用到反射了,所以要关注了一下反射的性能问题.搜索一下,不难搜到老赵的这篇文章,下面是一些杂乱的笔记.(建议先看老赵的文章) .Net4.0反射性能改善 看老赵的文章,老赵得到的结果是这样的: 1 00:00:00.0125539 (Directly invoke) 2 00:00:04.5349626 (Reflection invoke) 3 00:00:00.0322555 (Dynamic executor) 而我把代码搞下来自己运行得到这样的结果: 1

[转] 优化反射性能的总结(上)

反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题. 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性.字段). 那么如何得到委托呢? 目前最常见也就是二种方法:Emit, ExpressionTree .其中ExpressionTree可认为是Emit方法的简化版本, 所以Emit是最根本的方法,它采用在运行时动态构造一段IL代码来包装需要反射调用的代码, 这段动态生成的代码满足某个委托的签名,因此

C# 之 反射性能优化

反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题. 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性.字段). 目前最常见也就是二种方法:Emit, ExpressionTree .其中ExpressionTree可认为是Emit方法的简化版本, 所以Emit是最根本的方法,它采用在运行时动态构造一段IL代码来包装需要反射调用的代码, 这段动态生成的代码满足某个委托的签名,因此最后可以采用委托的方式