前言

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

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

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

性能对比数据

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

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

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

  1. 直接调用("htmlcode">
    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) })
       "{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> —— 自动生成多个类型的泛型 - 吕毅 一文中写了一个泛型生成器,可以稍加修改以便适应这种泛型类。

    总结

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

标签:
.netcore反射,.netcore性能,反射性能

免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
桃源资源网 Design By www.nqtax.com