反射是 C# 強大的動態特性,但效能代價有多大?本文透過實際基準測試,量化反射在物件建構、方法呼叫、靜態方法呼叫三大場景的效能損耗,並分析 IL 層面的根本原因。
public class CTester
{
private double _value;
public CTester() => _value = 10;
public void TestMethod() => _value = (_value - 0.0001) * 1.0001;
public double GetValue() => _value;
}
測試迴圈:1,000 外層 × 100 內層 = 100,000 次操作
| 方式 | 耗時 | 相對倍數 |
|---|---|---|
直接 new CTester() |
~16 ms | 1x (基準) |
反射 InvokeMember(含 Type.GetType 在迴圈內) |
~5,520 ms | ~350x |
反射 InvokeMember(Type.GetType 移至迴圈外) |
~1,332 ms | ~83x |
關鍵發現:Type.GetType() 重複呼叫是主要效能殺手。快取 Type 物件可大幅改善。
| 方式 | 耗時 | 相對倍數 |
|---|---|---|
直接呼叫 obj.TestMethod() |
~10 ms | 1x |
反射 InvokeMember("TestMethod") |
~120,000 ms (2 分) | ~250x |
| 方式 | 耗時 | 相對倍數 |
|---|---|---|
| 直接呼叫 | ~5 ms | 1x |
| 反射呼叫 | ~196,000 ms (3 分 16 秒) | ~3,900x |
使用 ILDASM 對比 IL 碼發現,直接呼叫產生 callvirt 指令,而反射呼叫轉為:
callvirt instance object [mscorlib]System.Type::InvokeMember(
string,
valuetype [mscorlib]System.Reflection.BindingFlags,
class [mscorlib]System.Reflection.Binder,
object,
object[]
)
核心開銷來源:
object[] 陣列、裝箱/拆箱值型別| 策略 | 適用場景 | 效能提升 |
|---|---|---|
| 快取 Type/MethodInfo/PropertyInfo | 重複使用相同反射操作 | 10-50x |
建立委派 Delegate.CreateDelegate |
頻繁呼叫同一方法 | 接近直接呼叫 (2-5x 差距) |
使用 DynamicMethod / IL Emit |
極高效能需求、動態生成程式碼 | 接近直接呼叫 |
| Expression Trees 編譯 Lambda | .NET 3.5+,動態建構邏輯 | 良好平衡開發效率與執行效能 |
| 原始碼產生器 / Roslyn | 編譯期已知型別結構 | 零執行期反射開銷 |
// 一次性建立委派,後續重複使用
var methodInfo = typeof(CTester).GetMethod("TestMethod");
var instance = Expression.Parameter(typeof(CTester), "instance");
var call = Expression.Call(instance, methodInfo);
var fastInvoker = Expression.Lambda>(call, instance).Compile();
// 使用
fastInvoker(tester); // 效能接近直接呼叫
反射是利器,但請在正確的場合、以正確的方式使用它。