前言
反射这种东西,大家都知道伤性能,但有的时候就是不得不用反射。那咋办呢?
那就是为反射得到的方法创建一个委托!
这种方式能够提高近乎直接调用方法本身的性能。 (当然Emit也能够帮助我们显著提升性能,不过直接得到可以调用的委托岂不是更加方便
咱们先创建一个类,用来做测试 1 2 3 4 5 6 7
| private class Foo { public int Test(int i) { return i; } }
|
先创建一个实例
1 2
| var instance = new Foo();
|
一般来说,我们调用方法直接可以这样写:
1
| var result = instance.Test(5);
|
这样写无疑是最不损耗性能的写法,所以是直接调用。
既然博文标题都说了是创建反射的委托,那咱们就先直接写个和Test方法功能一样的纯委托,用作性能对比测试
1 2
| Func<int, int> pureFunc = value => value;
|
下面是反射的使用,先写个通过反射拿到方法 1 2
| var method = typeof(Foo).GetMethod(nameof(Foo.Test), new[] { typeof(int) });
|
拿到方法后就是调用,如果是直接调用,那么就是
1
| var result = method.Invoke(instance, new object[] { 5 });
|
当然,这个是先用GetMethod拿到方法缓存后的调用。如果是直接使用反射调用的话
1
| var result = typeof(Foo).GetMethod(nameof(Foo.Test), new[] { typeof(int) })?.Invoke(instance, new object[] { 5 });
|
下面就是本文的重点
那就是将反射找到的方法创建一个委托
如何实现?
实现的关键就在于 MethodInfo.CreateDelegate
方法。这是 .NET Standard 中就有的方法,这意味着 .NET Framework 和 .NET Core 中都可以使用。
此方法有两个重载:
1.要求传入一个类型,而这个类型就是应该转成的委托的类型 2.要求传入一个类型和一个实例,一样的,类型是应该转成的委托的类型
他们的区别在于前者创建出来的委托是直接调用那个实例方法本身,后者则更原始一些,真正调用的时候还需要传入一个实例对象。
1 2 3
| var func = (Func<int, int>)method.CreateDelegate(typeof(Func<int, int>), instance); var func2 = (Func<Foo, int, int>)method.CreateDelegate(typeof(Func<Foo,int, int>));
|
前者得到的委托相当于 int Test(int i)
方法,后者得到的委托相当于 int Test(Foo instance, int i)
方法。(在 IL 里实例的方法其实都是后者,而前者更像 C# 中的代码,容易理解。)
以下是性能测试代码,可以更好的理解本文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| using System; using System.Diagnostics; using System.Reflection; using System.Diagnostics.Contracts;
namespace ConsoleApp10 { public class Program { static void Main(string[] args) { var instance = new Foo();
var method = typeof(Foo).GetMethod(nameof(Foo.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} 次 - 直接调用");
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(Foo).GetMethod(nameof(Foo.Test), new[] { typeof(int) }) ?.Invoke(instance, new object[] { 5 }); }
watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接使用反射调用");
Console.ReadLine(); }
private class Foo { public int Test(int i) { return i; } }
public static class InstanceMethodBuilder<T, TReturnValue> { [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); }
[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>)); } } } }
|
性能对比:
解释一下这五行数据的含义:
1.直接调用(应该没有什么比直接调用函数本身更有性能优势的吧)
2.做一个跟直接调用的方法功能一模一样的委托(目的是看看调用委托相比调用方法本身是否有性能损失,从数据上看,损失非常小)
3.本文重点 将反射出来的方法创建一个委托,然后调用这个委托(看看吧,性能跟直接调差别也不大嘛)
4.先反射得到方法,然后一直调用这个方法(终于可以看出来反射本身还是挺伤性能的了,50 多倍的性能损失啊)
5.缓存都不用,从头开始反射然后调用得到的方法(100 多倍的性能损失了)
封装:
单独使用 CreateDelegate
方法可能每次都需要尝试第一个参数到底应该传入些什么,于是我将其封装成了泛型版本,增加易用性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public static class InstanceMethodBuilder<T, TReturnValue> { [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); }
[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>)); } }
|
当然,这个泛型封装也是有问题的,也就是创建的委托只能是一个参数。没有实现像Action<T1,T2,T3,Tn>这样的多参数委托
不过多参数版本可以使用泛型类型生成器生成。
不过这个日后再说,之后有时间再给大家写一个泛型生成器