.NET Core/Framework如何创建委托大幅度提高反射调用的性能详解

前言

大家都知道反射伤性能,但不得不反射的时候又怎么办呢?当真的被问题逼迫的时候还是能找到解决办法的。

反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题。 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性、字段)。

为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能。(当然 Emit 也能够帮助我们显著提升性能,不过直接得到可以调用的委托不是更加方便吗?)

性能对比数据

▲ 没有什么能够比数据更有说服力(注意后面两行是有秒数的)

可能我还需要解释一下那五行数据的含义:

  1. 直接调用(😏应该没有什么比直接调用函数本身更有性能优势的吧)
  2. 做一个跟直接调用的方法功能一模一样的委托(😮目的是看看调用委托相比调用方法本身是否有性能损失,从数据上看,损失非常小)
  3. 本文重点 将反射出来的方法创建一个委托,然后调用这个委托(🤩看看吧,性能跟直接调差别也不大嘛)
  4. 先反射得到方法,然后一直调用这个方法(😥终于可以看出来反射本身还是挺伤性能的了,50 多倍的性能损失啊)
  5. 缓存都不用,从头开始反射然后调用得到的方法(😒100 多倍的性能损失了)

以下是测试代码,可以更好地理解上图数据的含义:

using System;
using System.Diagnostics;
using System.Reflection;

namespace Walterlv.Demo
{
 public class Program
 {
 static void Main(string[] args)
 {
  // 调用的目标实例。
  var instance = new StubClass();

  // 使用反射找到的方法。
  var method = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) });
  Assert.IsNotNull(method);

  // 将反射找到的方法创建一个委托。
  var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method);

  // 跟被测方法功能一样的纯委托。
  Func<int, int> pureFunc = value => value;

  // 测试次数。
  var count = 10000000;

  // 直接调用。
  var watch = new Stopwatch();
  watch.Start();
  for (var i = 0; i < count; i++)
  {
  var result = instance.Test(5);
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接调用");

  // 使用同样功能的 Func 调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = pureFunc(5);
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用同样功能的 Func 调用");

  // 使用反射创建出来的委托调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = func(5);
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射创建出来的委托调用");

  // 使用反射得到的方法缓存调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = method.Invoke(instance, new object[] { 5 });
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射得到的方法缓存调用");

  // 直接使用反射调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) })
   ?.Invoke(instance, new object[] { 5 });
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接使用反射调用");
 }

 private class StubClass
 {
  public int Test(int i)
  {
  return i;
  }
 }
 }
}

如何实现

实现的关键就在于 MethodInfo.CreateDelegate 方法。这是 .NET Standard 中就有的方法,这意味着 .NET Framework 和 .NET Core 中都可以使用。

此方法有两个重载:

  • 要求传入一个类型,而这个类型就是应该转成的委托的类型
  • 要求传入一个类型和一个实例,一样的,类型是应该转成的委托的类型

他们的区别在于前者创建出来的委托是直接调用那个实例方法本身,后者则更原始一些,真正调用的时候还需要传入一个实例对象。

拿上面的 StubClass 来说明会更直观一些:

private class StubClass
{
 public int Test(int i)
 {
 return i;
 }
}

前者得到的委托相当于 int Test(int i) 方法,后者得到的委托相当于 int Test(StubClass instance, int i) 方法。(在 IL 里实例的方法其实都是后者,而前者更像 C# 中的代码,容易理解。)

单独使用 CreateDelegate 方法可能每次都需要尝试第一个参数到底应该传入些什么,于是我将其封装成了泛型版本,增加易用性。

using System;
using System.Linq;
using System.Reflection;
using System.Diagnostics.Contracts;

namespace Walterlv.Demo
{
 public static class InstanceMethodBuilder<T, TReturnValue>
 {
 /// <summary>
 /// 调用时就像 var result = func(t)。
 /// </summary>
 [Pure]
 public static Func<T, TReturnValue> CreateInstanceMethod<TInstanceType>(TInstanceType instance, MethodInfo method)
 {
  if (instance == null) throw new ArgumentNullException(nameof(instance));
  if (method == null) throw new ArgumentNullException(nameof(method));

  return (Func<T, TReturnValue>) method.CreateDelegate(typeof(Func<T, TReturnValue>), instance);
 }

 /// <summary>
 /// 调用时就像 var result = func(this, t)。
 /// </summary>
 [Pure]
 public static Func<TInstanceType, T, TReturnValue> CreateMethod<TInstanceType>(MethodInfo method)
 {
  if (method == null)
  throw new ArgumentNullException(nameof(method));

  return (Func<TInstanceType, T, TReturnValue>) method.CreateDelegate(typeof(Func<TInstanceType, T, TReturnValue>));
 }
 }
}

泛型的多参数版本可以使用泛型类型生成器生成,我在 生成代码,从 <T> 到 <T1, T2, Tn> —— 自动生成多个类型的泛型 - 吕毅 一文中写了一个泛型生成器,可以稍加修改以便适应这种泛型类。

总结

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

(0)

相关推荐

  • 浅谈.NET反射机制的性能优化 附实例下载

    可能大家谈到反射面部肌肉都开始抽搐了吧!因为在托管语言里面,最臭名昭著的就是反射!它的性能实在是太低了,甚至在很多时候让我们无法忍受.不过不用那么纠结了,老陈今天就来分享一下如何来优化反射! 概述 本文涉及到的反射优化的途径有如下两种: 通过Delegate.CreateDelegate()创建委托进行优化 通过.NET4的动态运行时进行优化 如果您还知道其他更加有效的优化途径,请不吝赐教! 准备工作 今天我们总计要对比五种不同的调用对象成员的方式,也算是一种性能测评. 在开始之前,我们首先定义

  • .net中 关于反射的详细介绍

    概述反射• 通过反射可以提供类型信息,从而使得我们开发人员在运行时能够利用这些信息构造和使用对象. • 反射机制允许程序在执行过程中动态地添加各种功能.  运行时类型标识 •运行时类型标识(RTTI),可以在程序执行期间判定对象类型.例如使用它能够确切地知道基类引用指向了什么类型对象.•运行时类型标识,能预先测试某个强制类型转换操作,能否成功,从而避免无效的强制类型转换异常. •在c#中有三个支持RTTI的关键字:is . as  .typeof. 下面依次介绍他们  is运算符: 通过is运算

  • .NET/C#如何使用反射注册事件详解

    前言 通过放射,可以在运行时获得.NET中每一个类型(包括类.结构.委托.接口和枚举等)的成员,包括方法.属性.事件,以及构造函数等.还可以获得每个成员的名称.限定符和参数等.有了反射,即可对每一个类型了如指掌.如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知道.那么如何注册事件呢? 本文将介绍如何使用反射注册事件.下面话不多说了,来一起看看看详细的介绍吧 不使用反射 例如,我们希望反射的类型是这样的: public class Walterlv { public e

  • asp.net 反射减少代码书写量

    复制代码 代码如下: public bool Add(Liuyan refmodel)    {        string sql = "insert into liuyan(name,phone,zhiwei,gongsi,addr,country,dianyou,content,adddate)values(@name,@phone,@zhiwei,@gongsi,@addr,@country,@dianyou,@content,@adddate)";        OleDbP

  • .NET/C#利用反射调用含ref或out参数的方法示例代码

    前言 使用反射,我们可以很容易地在运行时调用一些编译时无法确定的属性.方法等.在.NET中的反射可以实现从对象的外部来了解对象(或程序集)内部结构的功能,哪怕你不知道这个对象(或程序集)是个什么东西,另外.NET中的反射还可以运态创建出对象并执行它其中的方法. 反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类.结构.委托.接口和枚举等)的成员和成员的信息.有了反射,即可对每一个类型了如指掌.另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道. 反射

  • asp.net反射简单应用实例

    本文实例讲述了asp.net反射简单应用.分享给大家供大家参考,具体如下: 反射提供了封装程序集.模块和类型的对象(Type 类型).可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性.如果代码中使用了属性,可以利用反射对它们进行访问.----这是反射最简单的理解.下面就是一个最简单的实例来讲述反射技术的应用! 一. 声明接口,接口中包含一个虚方法.如下 using System; using System.Collections.Gener

  • .NET Core/Framework如何创建委托大幅度提高反射调用的性能详解

    前言 大家都知道反射伤性能,但不得不反射的时候又怎么办呢?当真的被问题逼迫的时候还是能找到解决办法的. 反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题. 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性.字段). 为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能.(当然 Emit 也能够帮助我们显著提升性能,不过直接得到可以调用的委托不是更加方便吗?) 性能对比数据

  • 在 .NET 平台使用 ReflectionDynamicObject 优化反射调用的代码详解

    基于封装的原则,API 的设计者会将部分成员(属性.字段.方法等)隐藏以保证健壮性.但总有需要直接访问这些私有成员的情况. 为了访问一个类型的私有成员,除了更改 API 设计还有就是使用反射技术: public class MyApi { public MyApi() { _createdAt = DateTime.Now; } private DateTime _createdAt; public int ShowTimes { get; private set; } public void

  • Java 反射机制的实例详解

    Java 反射机制的实例详解 前言 今天介绍下Java的反射机制,以前我们获取一个类的实例都是使用new一个实例出来.那样太low了,今天跟我一起来学习学习一种更加高大上的方式来实现. 正文 Java反射机制定义 Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. 用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法. 反射

  • Java中反射机制和作用详解

    前言 很多刚学Java反射的同学可能对反射技术一头雾水,为什么要学习反射,学习反射有什么作用,不用反射,通过new也能创建用户对象. 那么接下来大师就带你们了解一下反射是什么,为什么要学习反射? 下面我们首先通过一个实例来说明反射的好处: 方法1.不用反射技术,创建用户对象,调用sayHello方法 1.1 我们首先创建一个User类 package com.dashi; /** * Author:Java大师 * User对象,包含用户的id和姓名以及sayHello方法 */ public

  • Golang 中反射的应用实例详解

    目录 引言 Golang类型设计原则 Golang 中为什么要使用反射/什么场景可以(应该)使用反射 举例场景: 反射的基本用法 反射的性能分析与优缺点 测试反射结构体初始化 测试结构体字段读取/赋值 测试结构体方法调用 优缺点 反射在 okr 中的简单应用 结论 引言 首先来一段简单的代码逻辑热身,下面的代码大家觉得应该会打印什么呢? type OKR struct { id int content string } func getOkrDetail(ctx context.Context,

  • oracle中创建序列及序列补零实例详解

    oracle中创建序列及序列补零实例详解 我们经常会在在DB中创建序列: -- Create sequence create sequence COMMON_SEQ minvalue 1 maxvalue 999999999 start with 1 increment by 1 cache 20 cycle; 我们的序列的最小值是从1开始,但是我们想让这种顺序取出来的序列的位数都一样,按照最大数的位数来算,我们需要8位的序列,那么我们就需要在1的前面补上7个零,只需要用下面的方法即可完成 se

  • python类:class创建、数据方法属性及访问控制详解

    在Python中,可以通过class关键字定义自己的类,然后通过自定义的类对象类创建实例对象. python中创建类 创建一个Student的类,并且实现了这个类的初始化函数"__init__": class Student(object):     count = 0     books = []     def __init__(self, name):         self.name = name 接下来就通过上面的Student类来看看Python中类的相关内容. 类构造和

  • Java反射框架Reflections示例详解

    MAVEN 坐标 <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency> Reflections 的作用 Reflections通过扫描classpath,索引元数据,并且允许在运行时查询这些元数据. 获取某个类型的所有

  • 使用 React 和 Threejs 创建一个VR全景项目的过程详解

    最近我在学习使用 React 配合 Three.js 来搭建一个可以浏览720全景图片的项目 实现的是加载一张 2:1 的720全景 分享一下我的创建过程 一.搭建框架并安装需要的插件 npx create-react-app parano // 创建一个 React 项目 npm install -S typescript // 安装 typescript,这个是类型辅助插件,与全景项目关系不大 npm install -S @types/three // 安装 typescript 支持的

  • ASP.NET Core扩展库之Http通用扩展库的使用详解

    本文将介绍Xfrogcn.AspNetCore.Extensions扩展库对于Http相关的其他功能扩展,这些功能旨在处理一些常见需求, 包括请求缓冲.请求头传递.请求头日志范围.针对HttpClient与HttpRequestMessage.HttpResponseMessage的扩展方法. 一.开启服务端请求缓冲 ASP.NET Core 中请求体是不能多次读取的,由于在MVC中,框架已经读取过请求体,如果你在控制器中再次读取,将会引发异常,如下示例: [ApiController] [Ro

随机推荐