可配置系統框架大量使用反射,需量化其效能代價。本文呈現最基礎的物件建構反射測試,揭示 350 倍效能差距與 Type.GetType 快取帶來的 20 倍優化。
namespace ReflectionTest.Test
{
public class CTester
{
private double _a = 10;
public CTester() { _a = 10; }
public void Test1() => _a = (_a - 0.0001) * 1.0001;
public double GetA() => _a;
}
}
private void TestDirectConstructor()
{
DateTime now = DateTime.Now;
for (int i = 0; i < 1000; i++)
for (int j = 0; j < 100; j++)
var obj = new CTester(); // 100,000 次
TimeSpan span = DateTime.Now - now;
label1.Text = $"Direct: {span}"; // ~16 ms
}
private void TestReflectConstructorUncached()
{
DateTime now = DateTime.Now;
for (int i = 0; i < 1000; i++)
for (int j = 0; j < 100; j++)
{
// 每次都呼叫 Type.GetType - 極度低效
Type t = Type.GetType("ReflectionTest.Test.CTester");
var obj = t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
}
TimeSpan span = DateTime.Now - now;
label2.Text = $"Reflect (uncached): {span}"; // ~5,520 ms
}
private void TestReflectConstructorCached()
{
Type t = Type.GetType("ReflectionTest.Test.CTester"); // 提前快取
DateTime now = DateTime.Now;
for (int i = 0; i < 1000; i++)
for (int j = 0; j < 100; j++)
var obj = t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
TimeSpan span = DateTime.Now - now;
label2.Text = $"Reflect (cached): {span}"; // ~1,332 ms
}
| 測試案例 | 耗時 | vs 直接呼叫 | 說明 |
|---|---|---|---|
| 直接 new | 16 ms | 1x (基準) | JIT 最佳化、直接記憶體配置 |
| 反射 (未快取 Type) | 5,520 ms | 350x | 每次解析型別字串、查找組件、載入中繼資料 |
| 反射 (快取 Type) | 1,332 ms | 83x | 移除 Type.GetType 開銷,剩餘 InvokeMember 解析成本 |
Type.GetType("Namespace.TypeName, AssemblyName") 執行:
RuntimeType 物件此過程涉及字串解析、鎖競爭、組件載入邏輯、中繼資料查找,極不適合放在迴圈中。
public static class ReflectionCache
{
private static readonly Dictionary _typeCache = new();
private static readonly Dictionary _ctorCache = new();
private static readonly object _lock = new();
public static Type GetType(string typeName)
{
if (_typeCache.TryGetValue(typeName, out var t)) return t;
lock (_lock)
{
if (_typeCache.TryGetValue(typeName, out t)) return t;
t = Type.GetType(typeName) ?? throw new ArgumentException($"Type not found: {typeName}");
_typeCache[typeName] = t;
return t;
}
}
public static ConstructorInfo GetConstructor(string typeName)
{
string key = $"ctor:{typeName}";
if (_ctorCache.TryGetValue(key, out var ctor)) return ctor;
lock (_lock)
{
if (_ctorCache.TryGetValue(key, out ctor)) return ctor;
var type = GetType(typeName);
ctor = type.GetConstructor(Type.EmptyTypes) ?? throw new ArgumentException($"No parameterless ctor: {typeName}");
_ctorCache[key] = ctor;
return ctor;
}
}
}
// 使用快取版本
var type = ReflectionCache.GetType("ReflectionTest.Test.CTester");
var ctor = ReflectionCache.GetConstructor("ReflectionTest.Test.CTester");
var obj = ctor.Invoke(null); // 比 InvokeMember 更快
// InvokeMember 通用但慢
t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
// ConstructorInfo.Invoke 專用於建構,較快
ctor.Invoke(null); // 少了 BindingFlags 解析、成員查找步驟
// 最快:Delegate.CreateDelegate
var factory = (Func)Delegate.CreateDelegate(typeof(Func), ctor);
var obj = factory(); // 接近直接 new 效能
反射建構物件比直接 new 慢 350 倍;快取 Type 可降至 83 倍;改用 ConstructorInfo + Delegate.CreateDelegate 可逼近直接呼叫效能。鐵律:熱點路徑禁用反射;必須用時,快取 MemberInfo、用強型別委派。