最近 IL Code 寫得比較多,主要是在練習,目的是希望自己將來遇到效能議題的時候,還有招數可以施展,剛開始練習寫 IL Code 的時候,是先從存取一個 instance 的公開或私有的屬性及欄位開始,這讓我想到一個套件 - FastMember,作者已經至少有 2 年沒有更新了,既然會一點 IL Code,那我能不能弄一套屬於自己的 Chef.FastMember
呢?
大概翻了一下 FastMember 的原始碼,大致上的概念是這樣的,當我們透過 FastMember 建立一個 TypeAccessor 時,FastMember 會去掃目標類別的屬性,並且為這些屬性的存取子(Getter
/Setter
)建立委派,之後透過這些委派去存取屬性的值。
TypeAccessor
接下來,我就依樣畫葫蘆,我不只建立屬性存取子的委派,還建立存取欄位值的委派方法,而且不只有公開的,連同私有的屬性及欄位也建立存取其值的委派方法,程式碼如下:
public class TypeAccessor
{
private static readonly ConcurrentDictionary<Type, Dictionary<string, Func<object, object>>> Getters = new ConcurrentDictionary<Type, Dictionary<string, Func<object, object>>>();
private static readonly ConcurrentDictionary<Type, Dictionary<string, Action<object, object>>> Setters = new ConcurrentDictionary<Type, Dictionary<string, Action<object, object>>>();
private readonly Dictionary<string, Func<object, object>> getters;
private readonly Dictionary<string, Action<object, object>> setters;
private TypeAccessor(Type type)
{
this.getters = Getters.GetOrAdd(type, t => CreateGetters(t));
this.setters = Setters.GetOrAdd(type, t => CreateSetters(t));
}
public object this[object target, string name]
{
get
{
if (!this.getters.TryGetValue(name, out var getter))
{
throw new ArgumentOutOfRangeException("name");
}
return getter(target);
}
set
{
if (!this.setters.TryGetValue(name, out var setter))
{
throw new ArgumentOutOfRangeException("name");
}
setter(target, value);
}
}
public static TypeAccessor Create(Type type)
{
return new TypeAccessor(type);
}
public static TypeAccessor Create<T>()
{
return Create(typeof(T));
}
// 建立取得屬性及欄位值的委派方法
private static Dictionary<string, Func<object, object>> CreateGetters(Type type)
{
var getters = new Dictionary<string, Func<object, object>>();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
var getterMethod = new DynamicMethod(property.Name, typeof(object), new[] { typeof(object) }, type, true);
var il = getterMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, property.GetMethod);
il.Emit(OpCodes.Box, property.PropertyType);
il.Emit(OpCodes.Ret);
getters.Add(property.Name, getterMethod.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>);
}
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
var getterMethod = new DynamicMethod(field.Name, typeof(object), new[] { typeof(object) }, type, true);
var il = getterMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Box, field.FieldType);
il.Emit(OpCodes.Ret);
getters.Add(field.Name, getterMethod.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>);
}
return getters;
}
// 建立賦予屬性及欄位值的委派方法
private static Dictionary<string, Action<object, object>> CreateSetters(Type type)
{
var setters = new Dictionary<string, Action<object, object>>();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
var setterMethod = new DynamicMethod(property.Name, null, new[] { typeof(object), typeof(object) }, type, true);
var il = setterMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Unbox_Any, property.PropertyType);
il.Emit(OpCodes.Callvirt, property.SetMethod);
il.Emit(OpCodes.Ret);
setters.Add(property.Name, setterMethod.CreateDelegate(typeof(Action<object, object>)) as Action<object, object>);
}
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
var setterMethod = new DynamicMethod(field.Name, null, new[] { typeof(object), typeof(object) }, type, true);
var il = setterMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Unbox_Any, field.FieldType);
il.Emit(OpCodes.Stfld, field);
il.Emit(OpCodes.Ret);
setters.Add(field.Name, setterMethod.CreateDelegate(typeof(Action<object, object>)) as Action<object, object>);
}
return setters;
}
}
重點在 CreateGetters()
及 CreateSetters()
這兩個方法,我把傳進來的 Type 中的所有公開及私有的屬性和欄位抓出來,為它們建立存取用的委派方法,並且快取起來,使用上也很簡單,就是建立一個 TypeAccessor,用 TypeAccessor 來存取屬性跟欄位的值。
internal class Program
{
private static void Main(string[] args)
{
object member = new Member();
var accessor = TypeAccessor.Create(member.GetType());
System.Console.WriteLine($"ABC={accessor[member, "ABC"]}");
System.Console.WriteLine($"Abc={accessor[member, "Abc"]}");
System.Console.WriteLine($"AbC={accessor[member, "AbC"]}");
System.Console.WriteLine($"abc={accessor[member, "abc"]}");
accessor[member, "ABC"] = 2;
accessor[member, "Abc"] = 23;
accessor[member, "AbC"] = 234;
accessor[member, "abc"] = 2345;
System.Console.WriteLine();
System.Console.WriteLine($"ABC={accessor[member, "ABC"]}");
System.Console.WriteLine($"Abc={accessor[member, "Abc"]}");
System.Console.WriteLine($"AbC={accessor[member, "AbC"]}");
System.Console.WriteLine($"abc={accessor[member, "abc"]}");
System.Console.Read();
}
}
public class Member
{
public int AbC = 11;
private int abc = 22;
public int ABC { get; set; } = 1;
private int Abc { get; set; } = 2;
}
我也用 BenchmarkDotNet 跑一下效能測試,如預期的,一般 Reflection 的 GetValue()/SetValue()
是比較慢的。
公開及私有方法也來
FastMember 這個套件只能存取屬性,既然都要自己寫了,我就連同方法也一起加進來能被呼叫,依照產生 Getters/Setters 委派方法的邏輯,補上 CreateFunctions()
。
public class TypeAccessor
{
// ...
private static readonly ConcurrentDictionary<Type, Dictionary<string, Func<object, object[], object>>> Functions = new ConcurrentDictionary<Type, Dictionary<string, Func<object, object[], object>>>();
// ...
private readonly Dictionary<string, Func<object, object[], object>> functions;
private TypeAccessor(Type type)
{
// ...
this.functions = Functions.GetOrAdd(type, t => CreateFunctions(t));
}
// ...
public object Invoke(object instance, string name, params object[] arguments)
{
if (!this.functions.TryGetValue(name, out var func))
{
throw new ArgumentOutOfRangeException("name");
}
return func(instance, arguments);
}
// ...
// 建立每個方法的委派方法
private static Dictionary<string, Func<object, object[], object>> CreateFunctions(Type type)
{
var functions = new Dictionary<string, Func<object, object[], object>>();
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
var invokedMethod = new DynamicMethod(method.Name, typeof(object), new[] { typeof(object), typeof(object[]) }, type, true);
var il = invokedMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
var parameters = method.GetParameters();
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Unbox_Any, parameter.ParameterType);
}
il.Emit(OpCodes.Call, method);
if (method.ReturnType != typeof(void))
{
il.Emit(OpCodes.Box, method.ReturnType);
}
else
{
il.Emit(OpCodes.Ldnull);
}
il.Emit(OpCodes.Ret);
functions.Add(method.Name, invokedMethod.CreateDelegate(typeof(Func<object, object[], object>)) as Func<object, object[], object>);
}
return functions;
}
}
這樣之後,無論公開或私有的屬性、欄位、方法,全部都存取得到。
internal class Program
{
private static void Main(string[] args)
{
object member = new Member();
var accessor = TypeAccessor.Create(member.GetType());
accessor.Invoke(member, "MyPublicVoid");
accessor.Invoke(member, "MyPrivateVoid");
System.Console.WriteLine();
System.Console.WriteLine($"MyPublicId={accessor.Invoke(member, "MyPublicId")}");
System.Console.WriteLine($"MyPrivateId={accessor.Invoke(member, "MyPrivateId")}");
System.Console.WriteLine();
System.Console.WriteLine($"MyPublicAdd(1, 2)={accessor.Invoke(member, "MyPublicAdd", 1, 2)}");
System.Console.WriteLine($"MyPrivateAdd(3, 4)={accessor.Invoke(member, "MyPrivateAdd", 3, 4)}");
System.Console.Read();
}
}
public class Member
{
// ...
public void MyPublicVoid()
{
System.Console.WriteLine("MyPublicVoid");
}
public int MyPublicId()
{
return 4;
}
public int MyPublicAdd(int a, int b)
{
return a + b;
}
private void MyPrivateVoid()
{
System.Console.WriteLine("MyPrivateVoid");
}
private int MyPrivateId()
{
return 5;
}
private int MyPrivateAdd(int a, int b)
{
return a + b;
}
}
也用 BenchmarkDotNet 與一般 Reflection 的 MethodInfo.Invoke()
做比較,結果如下:
當然這裡面還有很多的細節要處理,例如:靜態方法、覆寫方法、…等等,不過這此都不是問題了,語法不一樣而已,經過這一陣子的練習之後,往後再看到原始碼裡面有 IL Code 就不會那麼陌生了。