`
wyf
  • 浏览: 425281 次
  • 性别: Icon_minigender_1
  • 来自: 唐山
社区版块
存档分类
最新评论

使用反射 快速访问属性

    博客分类:
  • C#
阅读更多

反射非常适合访问任意类型的所有属性(或运行时命名的任意属性)。但是,Reflection的性能成本在关键代码路径中是不可接受的。相反,您可以添加前期成本并创建通用委托来读取或写入此类属性而不会产生任何开销(取决于您对值的操作,甚至可以避免装箱)。

这种代码的典型目标是获取具有指定名称的属性的值。

这种天真(和最慢)的方式是直接反射:

string name
object o;

object value = o.GetType().GetProperty(name).GetValue(o);

这样做的所有工作都是定位属性并在每次调用时动态调用其getter。您可以通过缓存每个来改善这一点PropertyInfo

var properties = new Dictionary<string, PropertyInfo>();

PropertyInfo property;
if (!properties.TryGetValue(name, out property))
	properties.Add(name, (property = o.GetType().GetProperty(o)));
value = property.GetValue(o);

(如果涉及多个线程,则需要[ThreadStatic]或者a ConcurrentDictionary

但是,这仍然重复动态调用getter方法的工作。您可以通过创建指向get方法的委托来消除此问题。创建委托仍然需要一些代价高昂的反映,但一旦创建,调用它将与调用任何其他委托一样快。

var getters = new Dictionary<string, Func<T, object>();

Func<T, object> getter;
if (!getters.TryGetValue(name, out getter)) {
	getter = (Func<T, object>)Delegate.CreateDelegate(
		typeof(Func<T, object>),
		typeof(T).GetProperty(name).GetMethod
	);
	getters.Add(name, getter);
}

T o;
value = getter(o);

这使用委托返回类型协方差Func<..., object>从getter方法创建一个返回任何更多派生类型的方法。这个特征实际上早于一般协方差; 它甚至可以在.Net 2.0上运行。

这种方法有一些警告。这只有在您知道在编译时操作的对象类型时才有效(例如,在绑定或序列化参数的通用方法中)。如果你object在编译时运行s,这是行不通的,因为这样的委托不是类型安全的。

这也不适用于返回值类型的属性。方差是有效的,因为类型的运行时表示完全相同,因此JITted代码不知道或不关心实际的类型参数是不同的。值类型需要与引用类型不同的codegen,因此无法开始工作。

要解决这些问题,您可以使用非常方便的方式生成实际的运行时代码Expression<T>具体来说,您需要创建一个将object参数强制转换为的表达式树T(如果您在编译时不知道实际类型),然后将该属性的结果打包成object(如果它是值类型)。

var param = Expression.Parameter(typeof(T));
getter = Expression.Lambda<Func<T, object>>(
	Expression.Convert(
		Expression.Property(param, name),
		typeof(object)
	),
	param
).Compile();

所述Expression.Convert()如果节点将自动产生一个装箱转换T为值类型,使得这项工作。只有T值类型才需要整个块如果它是引用类型,则可以使用前面的示例跳过整个运行时codegen阶段。

英文

Reflection is great for accessing all properties (or an arbitrary property named at runtime) of an arbitrary type. However, Reflection has performance costs which can be unacceptable in critical codepaths. Instead, you can add an upfront cost and create generic delegates to read or write such properties without any overhead at all (depending on what you’re doing with the values, you can even avoid boxing).

The typical goal for this kind of code is to get the value of a property with the specified name.

The naive (and slowest) way to do this is straight-up reflection:

string name
object o;

object value = o.GetType().GetProperty(name).GetValue(o);

This does all of the work of locating the property and dynamically invoking its getter on every call. You can improve this by caching each PropertyInfo:

var properties = new Dictionary<string, PropertyInfo>();

PropertyInfo property;
if (!properties.TryGetValue(name, out property))
	properties.Add(name, (property = o.GetType().GetProperty(o)));
value = property.GetValue(o);

(if multiple threads are involved, you’ll need either [ThreadStatic] or a ConcurrentDictionary)

However, this still repeats the work of dynamically invoking the getter method. You can eliminate this by creating a delegate that points to the get method. Creating the delegate still involves some costly reflection, but once it’s created, calling it will be as fast as calling any other delegate.

var getters = new Dictionary<string, Func<T, object>();

Func<T, object> getter;
if (!getters.TryGetValue(name, out getter)) {
	getter = (Func<T, object>)Delegate.CreateDelegate(
		typeof(Func<T, object>),
		typeof(T).GetProperty(name).GetMethod
	);
	getters.Add(name, getter);
}

T o;
value = getter(o);

This uses delegate return type covariance to create a Func<..., object> from a getter method that returns any more-derived type. This feature actually predates generic covariance; it will work even on .Net 2.0.

This approach has some caveats. This can only work if you know the type of the objects you’re operating on at compile-time (eg, in a generic method to bind or serialize parameters). If you’re operating on objects at compile time, this cannot work, since such a delegate wouldn’t be type-safe.

This also won’t work for properties that return value types. Variance works because the runtime representation of the types are completely identical, so that the JITted code doesn’t know or care that the actual type parameter is different. Value types require different codegen than reference types, so this cannot begin to work.

To solve these problems, you can do actual runtime code generation, using the ever-handy Expression<T>. Specifically, you need to create an expression tree that casts an object parameter to T (if you don’t know the actual type at compile time), then boxes the result of the property into an object (if it’s a value type).

var param = Expression.Parameter(typeof(T));
getter = Expression.Lambda<Func<T, object>>(
	Expression.Convert(
		Expression.Property(param, name),
		typeof(object)
	),
	param
).Compile();

The Expression.Convert() node will automatically generate a boxing conversion if T is a value type, making this work. This whole block is only necessary if T is a value type; if it’s a reference type, you can skip the entire runtime codegen phase with the previous example.

 

性能测试代码

   public class Class1<T>
    {
     

        public Dictionary<string, Func<T, object>> getters = new Dictionary<string, Func<T, object>>();
        public Dictionary<string, Func<T, object>> getters1 = new Dictionary<string, Func<T, object>>();
        public object a(T o, string name)
        {
            Func<T, object> getter;
            if (!getters.TryGetValue(name, out getter))
            {
                getter = (Func<T, object>)Delegate.CreateDelegate(
                    typeof(Func<T, object>),
                    typeof(T).GetProperty(name).GetMethod

                );
                getters.Add(name, getter);
            }
            return getter(o);
        }
        Dictionary<string, PropertyInfo> properties = new Dictionary<string, PropertyInfo>();
        public object b(T o, string name)
        {

            PropertyInfo property;
            if (!properties.TryGetValue(name, out property))
                properties.Add(name, (property = o.GetType().GetProperty(name)));
            return property.GetValue(o);
        }

        public object c(T o, string name)
        {
            Func<T, object> getter;
            if (!getters1.TryGetValue(name, out getter))
            {
                ParameterExpression param = Expression.Parameter(typeof(T));
                getter = Expression.Lambda<Func<T, object>>(
                    Expression.Convert(
                        Expression.Property(param, name),
                        typeof(object)
                    ),
                    param
                ).Compile();
                getters1.Add(name, getter);
            }

            return getter(o);
        }
    }

 调用

class Program
{
    static void Main()
    {

        S s = new S { Name = "qq" };
        Class1<S> c = new Class1<S>();
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int i = 0; i < 100000; i++)
        {
            c.a(s, "Name").ToString();

        }
        Console.WriteLine(stopwatch.ElapsedTicks);
     
        stopwatch.Restart();
        for (int i = 0; i < 100000; i++)
        {
            c.b(s, "Name").ToString();

        }
        Console.WriteLine(stopwatch.ElapsedTicks);
        stopwatch.Restart();
        for (int i = 0; i < 100000; i++)
        {
          c.c(s, "Name").ToString();

        }
        Console.WriteLine(stopwatch.ElapsedTicks);
        stopwatch.Stop();
        Console.ReadKey();

    }
    public class S
    {
        public string Name { get; set; }
    }

 结论:

耗时

26471

83130

143730

 

 

分享到:
评论

相关推荐

    accessify:一个工具库,可通过名称快速访问对象属性

    经常出现的功能是弄清楚如何通过名称而不使用反射来访问字段和属性(众所周知,速度很慢)。 所以我想我会使用一个简单的模式: interface PropertyHandler&lt;E&gt;{ void set(E instance, P propert

    基于Java的XML解析与反射设计模式.doc

    所以在读取大型xml时可以把 xstream与saxparser结合起来使用,用saxparser读取整个xml把核心数据部分让xstrea m来解析成javabean,这样既解决了大数据的问题又利用了xstream快速转化javabean的 优点。 为了利于多...

    DOTNET-C#基础快速入门教程-全网最简单

    DOTNET_C#基础快速入门教程,100多页的pdf文档,包括了常用的基础知识点: 数据类型 类型转换 变量、常量、运算符 字符串 流程控制 数组 结构、枚举 访问修饰符 方法 装箱和拆箱 接口 类和对象 多态 C# 预处理器 ...

    CLR.via.C#.(中文第3版)(自制详细书签)Part2

    · 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态扩展能力的应用程序 本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅...

    Spring.3.x企业应用开发实战(完整版).part2

    第11章 使用Spring JDBC访问数据库 11.1 使用Spring JDBC 11.1.1 JDBCTemplate小试牛刀 11.1.2 在DAO中使用JDBCTemplate 11.2 基本的数据操作 11.2.1 更改数据 11.2.2 返回数据库的表自增主键值 11.2.3 批量更改数据...

    Spring3.x企业应用开发实战(完整版) part1

    第11章 使用Spring JDBC访问数据库 11.1 使用Spring JDBC 11.1.1 JDBCTemplate小试牛刀 11.1.2 在DAO中使用JDBCTemplate 11.2 基本的数据操作 11.2.1 更改数据 11.2.2 返回数据库的表自增主键值 11.2.3 批量更改数据...

    PHP和MySQL WEB开发(第4版)

    6.10.10 使用Reflection(反射)API 6.11 下一章 第7章 错误和 异常处理 7.1 异常处理的概念 7.2 Exception类 7.3 用户自定义异常 7.4 Bob的汽车零部件商店应用程序的异常 7.5 异常和PHP的其他错误处理机制 7.6 ...

    PHP和MySQL Web开发第4版pdf以及源码

    6.10.10 使用Reflection(反射)API 6.11 下一章 第7章 错误和 异常处理 7.1 异常处理的概念 7.2 Exception类 7.3 用户自定义异常 7.4 Bob的汽车零部件商店应用程序的异常 7.5 异常和PHP的其他错误处理机制 ...

    PHP和MySQL Web开发第4版

    6.10.10 使用Reflection(反射)API 6.11 下一章 第7章 错误和 异常处理 7.1 异常处理的概念 7.2 Exception类 7.3 用户自定义异常 7.4 Bob的汽车零部件商店应用程序的异常 7.5 异常和PHP的其他错误处理机制 ...

    CLR.via.C#.(中文第3版)(自制详细书签)Part1

    10.4 属性访问器的可访问性 10.5 泛型属性访问器方法 第11章 事件 11.1 设计要公开事件的类型 11.1.1 第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息 11.1.2 第二步:定义事件成员 11.1.3 第...

    CLR.via.C#.(中文第3版)(自制详细书签)

    10.4 属性访问器的可访问性 10.5 泛型属性访问器方法 第11章 事件 11.1 设计要公开事件的类型 11.1.1 第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息 11.1.2 第二步:定义事件成员 11.1.3 第...

    CLR.via.C#.(中文第3版)(自制详细书签)Part3

    · 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态扩展能力的应用程序 本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅...

    asp.net知识库

    InternalsVisibleToAttribute,友元程序集访问属性 Essential .NET 读书笔记 [第一部分] NET FrameWork的Collections支持 .NET的反射在软件设计上的应用 关于跨程序集的反射 实现C#和VB.net之间的相互转换 深入剖析...

    Kotlin如何安全访问lateinit变量的实现

    Kotlin设计之初就是不允许非null变量在声明期间不进行初始化的,为了解决这个问题,Kotlin lateinit ...所以我们在 Kotlin 1.2及更高版本上,经常使用基于反射的API 快速检查lateinit属性是否已初始化。 private latei

    c# Linq WebService rss

    •Reflector: 使用 LINQ 对使用反射 API 的代码中的对象进行正确的查询。 •RSS: 此示例可作为聚合若干 RSS 源的小型 Web 服务器。 •SampleQueries: 这是最重要的示例,其中包含了 500 多个关于如何在 LINQ to ...

    fastreflect:A faster method to get elements from an interface (Struct or Slice type) for Go. 一个更快的方法, 用来获取 interface (Struct) 的属性, 或 interface (Slice) 的元素

    快速反射 (/) 一种从Go的接口(Struct或Slice类型)获取元素的更快方法。 用法 import ( ...) // Access an interface (struct type) property by name TheFieldYouWant := fastreflect ....通过名称快速访问接口(结

    这是一个基于QT,使用c++编写的2D超级玛丽游戏.zip

    Qt Meta-Object System(元对象系统)是Qt框架的一个重要特性,它引入了元对象编译器(moc),用于在编译时生成额外的代码以支持对象间通信、反射、动态属性绑定等高级功能。 Signal & Slot机制是Qt中实现对象间...

    ChineseChess 中国象棋,使用QT基于C++编写,实现了完整的人机对战.zip

    Qt Meta-Object System(元对象系统)是Qt框架的一个重要特性,它引入了元对象编译器(moc),用于在编译时生成额外的代码以支持对象间通信、反射、动态属性绑定等高级功能。 Signal & Slot机制是Qt中实现对象间...

    fast-member:自动从code.google.compfast-member导出

    快速访问.net字段/属性 在.NET中,反射速度很慢...嗯,有点慢。 如果您需要访问任意类型的成员,且类型和成员名称仅在运行时才知道-坦白讲,这很难(特别是对于DLR类型)。 该库使这种访问变得容易而快捷。 快速...

    Cisco路由器手册

    1.3.12 访问服务器 12 1.3.13 LAN扩展 13 第2章 Cisco路由器硬件 14 2.1 Cisco路由器网络分层 14 2.2 在线插拔 16 2.3 Cisco 12000系列 17 2.3.1 12000系列的使用 19 2.3.2 12000交换处理器 20 2.3.3 12000系列的...

Global site tag (gtag.js) - Google Analytics