前言
大家都知道反射傷性能,但不得不反射的時候又怎么辦呢?當真的被問題逼迫的時候還是能找到解決辦法的。
反射是一種很重要的技術,然而它與直接調用相比性能要慢很多,因此如何優化反射性能也就成為一個不得不面對的問題。 目前最常見的優化反射性能的方法就是采用委托:用委托的方式調用需要反射調用的方法(或者屬性、字段)。
為反射得到的方法創建一個委托,此后調用此委托將能夠提高近乎直接調用方法本身的性能。(當然 Emit 也能夠幫助我們顯著提升性能,不過直接得到可以調用的委托不是更加方便嗎?)
性能對比數據
▲ 沒有什么能夠比數據更有說服力(注意后面兩行是有秒數的)
可能我還需要解釋一下那五行數據的含義:
以下是測試代碼,可以更好地理解上圖數據的含義:
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> —— 自動生成多個類型的泛型 - 呂毅 一文中寫了一個泛型生成器,可以稍加修改以便適應這種泛型類。
總結
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com