非公開的型別或者方法被「隱藏」在程式集內部,本就不希望從外部存取,但是有時候呼叫一個內部或者私有方法可能是唯一的「救命稻草」,這篇文章列出了幾種具體的實現方式。以如下這個Foobar型別為例,它具有一個內部屬性InternalValue,我們來看看有多少種方式可以從外部獲取一個Foobar物件的InternalValue屬性值。
public class Foobar { internal int InternalValue => 123; }
對於大部分人來說,最先想到的自然是「反射」,具體實現體現再如下所示的InternalValueAccessor型別的GetInternalValue方法中。但是我們都知道反射是一種並不高效的方式,對於需要頻繁呼叫,我們一般不推薦使用。
var foobar = new Foobar(); Debug.Assert(InternalValueAccessor.GetInternalValue(foobar) == 123); public static class InternalValueAccessor { public static int GetInternalValue(Foobar foobar) { var propertyInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!; return (int)propertyInfo.GetValue(foobar)!; } }
要獲得Foobar物件的InternalValue屬性值(int型別),實際上需要一個Func<Foobar,int>型別的委託。由於返回值實際上是通過InternalValue屬性的Get方法獲得的,而表示方法的MethodInfo型別具有一個CreateDelegate<TDelegate>方法,我們可以採用如下的方式利用InternalValue屬性的Get方法來建立所需的Func<Foobar,int>委託。
var foobar = new Foobar(); Debug.Assert(InternalValueAccessor.GetInternalValue(foobar) == 123); public static class InternalValueAccessor { private static Func<Foobar, int>? _getInternalValue; public static int GetInternalValue(Foobar foobar)=> (_getInternalValue??= CreateDelegate())(foobar); private static Func<Foobar, int> CreateDelegate() { var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!; return methodInfo.CreateDelegate<Func<Foobar, int>>(); } }
一般來說,所有的反射解決方案都可以轉換成基於表示式(樹)的解決方案。我們需要的Func<Foobar,int>委託可以按照如下的方式,利用構建的表示式編譯生成。
public static class InternalValueAccessor { private static Func<Foobar, int>? _getInternalValue; public static int GetInternalValue(Foobar foobar)=> (_getInternalValue??= CreateDelegate())(foobar); private static Func<Foobar, int> CreateDelegate() { var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!; var foobar = Expression.Parameter(typeof(Foobar), "foobar"); var getValue = Expression.Call(foobar, methodInfo); return Expression.Lambda<Func<Foobar, int>>(getValue, foobar).Compile(); } }
實際上表示式(樹)是對IL程式碼的抽象表達,所以既然這樣的問題自然可以利用IL Emit來解決。在如下的程式碼中,我們建立了一個DynamicMethod型別表示的動態方法,以IL Emit的方式利用IL指令Call完成了針對InternalValue屬性的Get方法的呼叫。我們所需的Func<Foobar,int>委託最終由這個DynamicMethod物件建立而成。
public static class InternalValueAccessor { private static Func<Foobar, int>? _getInternalValue; public static int GetInternalValue(Foobar foobar) => (_getInternalValue ??= CreateDelegate())(foobar); private static Func<Foobar, int> CreateDelegate() { var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!; var method = new DynamicMethod("GetInternalValue", typeof(int), new Type[] { typeof(Foobar) }); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.EmitCall(OpCodes.Call, methodInfo, null); il.Emit(OpCodes.Ret); return method.CreateDelegate<Func<Foobar, int>>(); } }
瞭解IL的朋友應該知道,方法呼叫涉及的IL治理有三個(Call、Callvir和Calli)。如果使用Calli指令,在完成針對引數的壓棧之後,我們還需要執行Ldftn指令將方法指標壓入棧中,最終執行Calli指令完成方法的執行。
public static class InternalValueAccessor { private static Func<Foobar, int>? _getInternalValue; public static int GetInternalValue(Foobar foobar) => (_getInternalValue ??= CreateDelegate())(foobar); private static Func<Foobar, int> CreateDelegate() { var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!; var method = new DynamicMethod("GetInternalValue", typeof(int), new Type[] { typeof(Foobar) }); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldftn, methodInfo); il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, typeof(int), new Type[] { typeof(Foobar) }, null); il.Emit(OpCodes.Ret); return method.CreateDelegate<Func<Foobar, int>>(); } }