利用lambda表达式树优化反射详解

前言

本节重点不讲反射机制,而是讲lambda表达式树来替代反射中常用的获取属性和方法,来达到相同的效果但却比反射高效。

每个人都知道,用反射调用一个方法或者对属性执行SetValue和GetValue操作的时候都会比直接调用慢很多,这其中设计到CLR中内部的处理,不做深究。然而,我们在某些情况下又无法不使用反射,比如:在一个ORM框架中,你要将一个DataRow转化为一个对象,但你又不清楚该对象有什么属性,这时候你就需要写一个通用的泛型方法来处理,以下代码写得有点恶心,但不妨碍理解意思:

//将DataReader转化为一个对象
     private static T GetObj<T>(SqliteDataReader reader) where T : class
 {
  T obj = new T();
  PropertyInfo[] pros = obj.GetType().GetProperties();
  foreach (PropertyInfo item in pros)
  {
  try
  {
   Int32 Index = reader.GetOrdinal(item.Name);
   String result = reader.GetString(Index);
   if (typeof(String) == item.PropertyType)
   {
   item.SetValue(obj, result);
   continue;
   }
   if (typeof(DateTime) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToDateTime(result));
   continue;
   }
   if (typeof(Boolean) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToBoolean(result));
   continue;
   }
   if (typeof(Int32) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToInt32(result));
   continue;
   }
   if (typeof(Single) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToSingle(result));
   continue;
   }
   if (typeof(Single) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToSingle(result));
   continue;
   }
   if (typeof(Double) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToDouble(result));
   continue;
   }
   if (typeof(Decimal) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToDecimal(result));
   continue;
   }
   if (typeof(Byte) == item.PropertyType)
   {
   item.SetValue(obj, Convert.ToByte(result));
   continue;
   }
  }
  catch (ArgumentOutOfRangeException ex)
  {
   continue;
  }
  }
  return obj;
 }

对于这种情况,其执行效率是特别低下的,具体多慢在下面例子会在.Net Core平台上和.Net Framework4.0运行测试案例.对于以上我举例的情况,效率上我们还可以得到提升。但对于想在运行时修改一下属性的名称或其他操作,反射还是一项特别的神器,因此在某些情况下反射还是无法避免的。

但是对于只是简单的SetValue或者GetValue,包括用反射构造函数,我们可以想一个中继的方法,那就是使用表达式树。对于不理解表达式树的,可以到微软文档查看,点击我。表达式树很容易通过对象模型表示表达式,因此强烈建议学习。查看以下代码:

static void Main()
 {
  Dog dog = new Dog();
  PropertyInfo propertyInfo = dog.GetType().GetProperty(nameof(dog.Name)); //获取对象Dog的属性
  MethodInfo SetterMethodInfo = propertyInfo.GetSetMethod(); //获取属性Name的set方法

  ParameterExpression param = Expression.Parameter(typeof(Dog), "param");
  Expression GetPropertyValueExp = Expression.Lambda(Expression.Property(param, nameof(dog.Name)), param);
  Expression<Func<Dog, String>> GetPropertyValueLambda = (Expression<Func<Dog, String>>)GetPropertyValueExp;
  ParameterExpression paramo = Expression.Parameter(typeof(Dog), "param");
  ParameterExpression parami = Expression.Parameter(typeof(String), "newvalue");
  MethodCallExpression MethodCallSetterOfProperty = Expression.Call(paramo, SetterMethodInfo, parami);
  Expression SetPropertyValueExp = Expression.Lambda(MethodCallSetterOfProperty, paramo, parami);
  Expression<Action<Dog, String>> SetPropertyValueLambda = (Expression<Action<Dog, String>>)SetPropertyValueExp;

  //创建了属性Name的Get方法表达式和Set方法表达式,当然只是最简单的
  Func<Dog, String> Getter = GetPropertyValueLambda.Compile();
  Action<Dog, String> Setter = SetPropertyValueLambda.Compile();

  Setter?.Invoke(dog, "WLJ"); //我们现在对dog这个对象的Name属性赋值
  String dogName = Getter?.Invoke(dog); //获取属性Name的值

  Console.WriteLine(dogName);
  Console.ReadKey();
 }

 public class Dog
 {
  public String Name { get; set; }
 }

以下代码可能很难看得懂,但只要知道我们创建了属性的Get、Set这两个方法就行,其结果最后也能输出狗的名字 WLJ,拥有ExpressionTree的好处是他有一个名为Compile()的方法,它创建一个代表表达式的代码块。现在是最有趣的部分,假设你在编译时不知道类型(在这篇文章中包含的代码我在不同的程序集上创建了一个类型)你仍然可以应用这种技术,我将对于常用的属性的set,get操作进行分装。

/// <summary>
   /// 属性类,仿造反射中的PropertyInfo
 /// </summary>
   public class Property
 {

  private readonly PropertyGetter getter;
  private readonly PropertySetter setter;
  public String Name { get; private set; }

  public PropertyInfo Info { get; private set; }

  public Property(PropertyInfo propertyInfo)
  {
   if (propertyInfo == null)
    throw new NullReferenceException("属性不能为空");
   this.Name = propertyInfo.Name;
   this.Info = propertyInfo;
   if (this.Info.CanRead)
   {
    this.getter = new PropertyGetter(propertyInfo);
   }

   if (this.Info.CanWrite)
   {
    this.setter = new PropertySetter(propertyInfo);
   }
  }

  /// <summary>
     /// 获取对象的值
  /// </summary>
    /// <param name="instance"></param>
    /// <returns></returns>
     public Object GetValue(Object instance)
  {
   return getter?.Invoke(instance);
  }

  /// <summary>
     /// 赋值操作
  /// </summary>
    /// <param name="instance"></param>
    /// <param name="value"></param>
     public void SetValue(Object instance, Object value)
  {
   this.setter?.Invoke(instance, value);
  }

  private static readonly ConcurrentDictionary<Type, Core.Reflection.Property[]> securityCache = new ConcurrentDictionary<Type, Property[]>();

  public static Core.Reflection.Property[] GetProperties(Type type)
  {
   return securityCache.GetOrAdd(type, t => t.GetProperties().Select(p => new Property(p)).ToArray());
  }

 }

  /// <summary>
   /// 属性Get操作类
  /// </summary>
    public class PropertyGetter
  {
  private readonly Func<Object, Object> funcGet;

  public PropertyGetter(PropertyInfo propertyInfo) : this(propertyInfo?.DeclaringType, propertyInfo.Name)
  {

  }

  public PropertyGetter(Type declareType, String propertyName)
  {
   if (declareType == null)
   {
    throw new ArgumentNullException(nameof(declareType));
   }
   if (propertyName == null)
   {
    throw new ArgumentNullException(nameof(propertyName));
   }

   this.funcGet = CreateGetValueDeleagte(declareType, propertyName);
  }

  //代码核心部分
     private static Func<Object, Object> CreateGetValueDeleagte(Type declareType, String propertyName)
  {
   // (object instance) => (object)((declaringType)instance).propertyName

       var param_instance = Expression.Parameter(typeof(Object));
   var body_objToType = Expression.Convert(param_instance, declareType);
   var body_getTypeProperty = Expression.Property(body_objToType, propertyName);
   var body_return = Expression.Convert(body_getTypeProperty, typeof(Object));
   return Expression.Lambda<Func<Object, Object>>(body_return, param_instance).Compile();
  }

  public Object Invoke(Object instance)
  {
   return this.funcGet?.Invoke(instance);
  }
 }

  public class PropertySetter
 {
  private readonly Action<Object, Object> setFunc;

  public PropertySetter(PropertyInfo property)
  {
   if (property == null)

   {
    throw new ArgumentNullException(nameof(property));
   }
   this.setFunc = CreateSetValueDelagate(property);
  }

  private static Action<Object, Object> CreateSetValueDelagate(PropertyInfo property)
  {
   // (object instance, object value) =>
   //  ((instanceType)instance).Set_XXX((propertyType)value)

   //声明方法需要的参数
   var param_instance = Expression.Parameter(typeof(Object));
   var param_value = Expression.Parameter(typeof(Object));

   var body_instance = Expression.Convert(param_instance, property.DeclaringType);
   var body_value = Expression.Convert(param_value, property.PropertyType);
   var body_call = Expression.Call(body_instance, property.GetSetMethod(), body_value);

   return Expression.Lambda<Action<Object, Object>>(body_call, param_instance, param_value).Compile();
  }

  public void Invoke(Object instance, Object value)
  {
   this.setFunc?.Invoke(instance, value);
  }
 }

在将代码应用到实例:

   Dog dog = new Dog();
   PropertyInfo propertyInfo = dog.GetType().GetProperty(nameof(dog.Name));

   //反射操作
   propertyInfo.SetValue(dog, "WLJ");
   String result = propertyInfo.GetValue(dog) as String;
   Console.WriteLine(result);

   //表达式树的操作
   Property property = new Property(propertyInfo);
   property.SetValue(dog, "WLJ2");
   String result2 = propertyInfo.GetValue(dog) as String;
   Console.WriteLine(result2);

发现其实现的目的与反射一致,但效率却有明显的提高。

以下测试以下他们两之间的效率。测试代码如下:

   Student student = new Student();
   PropertyInfo propertyInfo = student.GetType().GetProperty(nameof(student.Name));
   Property ExpProperty = new Property(propertyInfo);

   Int32 loopCount = 1000000;
   CodeTimer.Initialize(); //测试环境初始化

   //下面该方法个执行1000000次

   CodeTimer.Time("基础反射", loopCount, () => {
    propertyInfo.SetValue(student, "Fode",null);
   });
   CodeTimer.Time("lambda表达式树", loopCount, () => {
    ExpProperty.SetValue(student, "Fode");
   });
   CodeTimer.Time("直接赋值", loopCount, () => {
    student.Name = "Fode";
   });
   Console.ReadKey();

其.Net4.0环境下运行结果如下:

.Net Core环境下运行结果:

从以上结果可以知道,迭代同样的次数反射需要183ms,而用表达式只要34ms,直接赋值需要7ms,在效率上,使用表达式这种方法有显著的提高,您可以看到使用此技术可以完全避免使用反射时的性能损失。反射之所以效率有点低主要取决于其加载的时候时在运行期下,而表达式则在编译期,下篇有空将会介绍用Emit技术优化反射,会比表达式略快一点。

注:对于常用对象的属性,最好将其缓存起来,这样效率会更高。。

代码下载

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 详解Golang利用反射reflect动态调用方法

    编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制.也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 每种语言的反射模型都不同,并且有些语言根本不支持反射.Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用. 多插一句,

  • 实例讲解Java基础之反射

    前期准备 编写一个真实类phone,实现list接口 public class Phone implements List { public double price; public String name; public Phone() { } public Phone(double price, String name) { this.price = price; this.name = name; } public double getPrice() { return price; } p

  • 基于Java反射的map自动装配JavaBean工具类设计示例代码

    前言 JavaBean是一个特殊的java类,本文将给大家详细介绍关于基于Java反射的map自动装配JavaBean工具类设计的相关内容,下面话不多说了,来一起看看详细的介绍吧 方法如下 我们平时在用Myabtis时不是常常需要用map来传递参数,大体是如下的步骤: public List<Role> findRoles(Map<String,Object> param); <select id="dindRoles" parameterType=&qu

  • 使用反射机制控制Toast的显示时间

    本文为大家分享了使用反射机制控制Toast显示时间的具体代码,供大家参考,具体内容如下 1.Toast源码分析: Toast的默认view是在transient_notification.xml中定义的一个TextView,如果需要设置Toast的界面,可以通过setView方法实现:如果需要设置Toast默认显示的位置,可以通过setGravity或者setMargin方法进行设置,值得一提的是setMargin方法的参数范围是0-1即它是屏幕的百分比,如setMargin(0.1,0.1).

  • 通过反射注解批量插入数据到DB的实现方法

    批量导入思路 最近遇到一个需要批量导入数据问题.后来考虑运用反射做成一个工具类,思路是首先定义注解接口,在bean类上加注解,运行时通过反射获取传入Bean的注解,自动生成需要插入DB的SQL,根据设置的参数值批量提交.不需要写具体的SQL,也没有DAO的实现,这样一来批量导入的实现就和具体的数据库表彻底解耦.实际批量执行的SQL如下: insert into company_candidate(company_id,user_id,card_id,facebook_id,type,create

  • Java反射机制的精髓讲解

    1,什么是反射? java的反射,允许程序在运行时,创建一个对象,获取一个类的所有相关信息等. 2,Class类 要了解反射,就绕不开Class类. 我们平时开发的类,例如ClassA,一般会有一些属性,会有几个构造方法,也会有一些普通方法,我们还可以使用ClassA来创建对象,例如ClassA classA = new ClassA(). java程序在运行时,其实是很多类的很多个对象之间的协作.jvm如何管理这些类呢?它如何知道各个类的名称,每个类都有哪些属性和哪些方法呢? jvm会给每个类

  • Java高级特性之反射机制实例详解

    本文实例讲述了Java高级特性之反射机制.分享给大家供大家参考,具体如下: 老规矩我们还是先提出几个问题,一门技术必然要能解决一定的问题,才有去学习掌握它的价值 一. 什么是反射? 二.反射能做什么? 一. 什么是反射? 用在Java身上指的是我们可以于运行时加载.探知.使用编译期间完全未知的classes.换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体.或对其fields设值.或唤起其methods. 如果你是一个

  • 实例讲解Java中动态代理和反射机制

    反射机制 Java语言提供的一种基础功能,通过反射,我们可以操作这个类或对象,比如获取这个类中的方法.属性和构造方法等. 动态代理:分为JDK动态代理.cglib动态代理(spring中的动态代理). 静态代理 预先(编译期间)确定了代理者与被代理者之间的关系,也就是说,若代理类在程序运行前就已经存在了,这种情况就叫静态代理 动态代理 代理类在程序运行时创建的代理方式.也就是说,代理类并不是在Java代码中定义的,而是在运行期间根据我们在Java代码中的"指示"动态生成的. 动态代理比

  • Kotlin中的反射机制深入讲解

    前言 Java中的反射机制,使得我们可以在运行期获取Java类的字节码文件中的构造函数,成员变量,成员函数等信息.这一特性使得反射机制被常常用在框架中,想要比较系统的了解Kotlin中的反射,先从Java的反射说起. Java中的反射 通常我们写好的.java源码文件,经过javac的编译,最终生成了.class字节码文件.这些字节码文件是与平台无关的,使用时通过Classloader去加载这些.class字节码文件,从而让程序按照我们编写好的业务逻辑运行.Java的反射主要是从这些.class

  • ES6 如何改变JS内置行为的代理与反射

    代理(Proxy)可以拦截并改变 JS 引擎的底层操作,如数据读取.属性定义.函数构造等一系列操作.ES6 通过对这些底层内置对象的代理陷阱和反射函数,让开发者能进一步接近 JS 引擎的能力. 一.代理与反射的基本概念 什么是代理和反射呢? 代理是用来替代另一个对象(target),JS 通过new Proxy()创建一个目标对象的代理,该代理与该目标对象表面上可以被当作同一个对象来对待. 当目标对象上的进行一些特定的底层操作时,代理允许你拦截这些操作并且覆写它,而这原本只是 JS 引擎的内部能

随机推荐