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

为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能。(当然 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) });

            // 将反射找到的方法创建一个委托。            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;            }        }    }}

上面的代码中,有一个我们还没有实现的 InstanceMethodBuilder 类型,接下来将介绍如何实现它。

如何实现

实现的关键就在于 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> —— 自动生成多个类型的泛型 - 吕毅 一文中写了一个泛型生成器,可以稍加修改以便适应这种泛型类。

原文地址:https://blog.walterlv.com/post/create-delegate-to-improve-reflection-performance.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

.NET Core/Framework 创建委托以大幅度提高反射调用的性能相关推荐

  1. .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)...

    大家都说反射耗性能,但是到底有多耗性能,哪些反射方法更耗性能:这些问题却没有统一的描述. 本文将用数据说明反射各个方法和替代方法的性能差异,并提供一些反射代码的编写建议.为了解决反射的性能问题,你可以 ...

  2. 方法的直接调用,反射调用与……Lambda表达式调用

    方法的直接调用,反射调用与--Lambda表达式调用 2008-11-24 09:59 by Jeffrey Zhao, 32557 阅读, 100 评论, 收藏, 编辑 想调用一个方法很容易,直接代 ...

  3. java lambda 反射_反射调用与Lambda表达式调用

    想调用一个方法很容易,直接代码调用就行,这人人都会.其次呢,还可以使用反射.不过通过反射调用的性能会远远低于直接调用--至少从绝对时间上来看的确是这样.虽然这是个众所周知的现象,我们还是来写个程序来验 ...

  4. 利用反射获得委托和事件以及创建委托实例和添加事件处理程序

    最近一些都在看关于反射的内容,然后在网上大多数都是通过反射获得类型中方法,属性.字段这样的文章, 但是对于如何获得委托类型怎么去实现的却没有, 所以写下这边篇文章来让自己以后很好的复习以及想了解的朋友 ...

  5. ASP.NET Core 配置 - 创建自定义配置提供程序

    ASP.NET Core 配置 - 创建自定义配置提供程序 在本文中,我们将创建一个自定义配置提供程序,从数据库读取我们的配置.我们已经了解了默认配置提供程序的工作方式,现在我们将实现我们自己的自定义 ...

  6. 在ASP.NET Core中创建自定义端点可视化图

    在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...

  7. 【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务

    在我的上一篇文章<在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度>,我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计 ...

  8. Orchard Core Framework:ASP.NET Core 模块化,多租户框架

    上一篇编写Orchard Core一分钟搭建ASP.NET Core CMS ,介绍ASP.NET Core CMS ,Orchard的ASP.NET Core版,同时对应有一个ASP.NET Cor ...

  9. Spring教程– Spring Core Framework教程

    Spring is one of the most widely used Java EE frameworks. I have written a lot on Spring Tutorial an ...

最新文章

  1. 待遇46K起,这几个公众号在招人!
  2. js 数组遍历符合条件跳出循环体_Javascript数组循环遍历之forEach详解
  3. 学会利用杠杆,阻碍你成功的,或许恰恰是你认为正确的思维,能力、效率、杠杆三个因素,决定了你一生的发展...
  4. python 在互联网应用是如此强大
  5. WIN8 启用虚拟AP 以共享网络,使手机电脑一起网上冲浪
  6. 鸟哥:程序员应该不断提升自身的不可替代性
  7. 『设计模式』之小试牛刀
  8. html中如何显示纯文本,从Html中取出纯文本
  9. Java正则表达式, 提取双引号中间的部分
  10. Facebook提出Pica模型,为Quest 2带来实时逼真虚拟化身渲染
  11. SpringMVC-day01
  12. apache和php结合、apache的默认虚拟主机
  13. Crack内网通积分规则(基于版本3.4.3035)
  14. 使用xpath批量爬取堆糖图片
  15. linux监控线程运行状态,linux查看线程状态--jstack
  16. 拼多多商品APi、商品详情、产品页面信息接口
  17. 将文件放到Android模拟器的SD卡中的两种解决方法
  18. everbox邀请码和麦库邀请码
  19. Vue 点击复制粘贴文本
  20. PHP隐形一句话后门,和ThinkPHP框架加密码程序

热门文章

  1. chromebook刷机_如何为不支持Chrome操作系统的网站欺骗Chromebook用户代理
  2. 笔记本本地连接显示电缆拔出_没有安全电缆槽的笔记本电脑如何固定?
  3. AWD-LSTM为什么这么棒?
  4. 分析拼多多的崛起【产品思维】
  5. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
  6. oracle导出数据库中表出现导出报错(EXP-00003)未找到段 (0,0) 的存储定义
  7. 企业如何用CRM软件客户管理自动化优化流程?
  8. lost connection to MySQL server at waiting for initial communication packet,system error:o
  9. 如何使用Instruments诊断App(Swift版):起步
  10. hdu 2579 BFS