Created
December 4, 2012 11:53
-
-
Save hatelove/4203022 to your computer and use it in GitHub Desktop.
Dynamic Proxy by 黃忠成 - 程式碼註解 by 91
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// http://www.dotblogs.com.tw/code6421/archive/2012/12/03/85339.aspx | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
using System.Text; | |
namespace SimpleImpl | |
{ | |
// 當作等等IL動態新增物件的基底類別 | |
public class BaseProxy | |
{ | |
// IL會去使用到_methodResults這個field | |
public Dictionary<string, object> _methodResults = null; | |
// 給context端呼叫的,用來模擬stub目標物件,什麼方法(dictionary key: string)預計回傳什麼結果(dictionary value: object) | |
public void SetMethodResults(Dictionary<string, object> methodResults) | |
{ | |
_methodResults = methodResults; | |
} | |
// 提供給IL產生動態物件時,可以直接呼叫這個方法 | |
public object GetMethodResult(string methodName) | |
{ | |
if (_methodResults.ContainsKey(methodName)) | |
return _methodResults[methodName]; | |
return null; | |
} | |
} | |
public class MyMock | |
{ | |
// 要使用IL動態產生的物件型別 | |
private Type _tpType = null; | |
// 要mock的型別,通常是介面 | |
private Type _proxyType = null; | |
private Dictionary<string, object> _methodResults = new Dictionary<string, object>(); | |
public MyMock(Type proxyType) | |
{ | |
_proxyType = proxyType; | |
} | |
public object GetReturnValue(string methodName) | |
{ | |
return _methodResults[methodName]; | |
} | |
public void SetMethodResult(string methodName, object result) | |
{ | |
if (_methodResults.ContainsKey(methodName)) | |
_methodResults[methodName] = result; | |
else | |
_methodResults.Add(methodName, result); | |
} | |
// 回傳用IL產生的proxy object | |
public object GetTransparentProxy() | |
{ | |
if (_tpType == null) | |
{ | |
// 透過IL產生動態的proxy object type,其基底類型是BaseProxy,假設名字叫做Dynamic$1234 | |
TypeBuilder tb = GetTypeBuilder(); | |
// 針對Dynamic$1234型別,建立一個constructor,參數只有一個,型別為Dictionary<string,object> | |
ConstructorBuilder constructor = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | | |
MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] { typeof(Dictionary<string, object>) }); | |
ILGenerator il = constructor.GetILGenerator(); | |
// 將 Dynamic$1234,也就是 this 推進堆疊 | |
il.Emit(OpCodes.Ldarg_0); | |
// 取得第一個參數,也就是剛剛constructor裡面的parameter: Dictionary<string,object> | |
il.Emit(OpCodes.Ldarg_1); | |
// 把上一行的值,也就是constructor的parameter,放到BaseProxy的field: _methodResults 裡面 | |
il.Emit(OpCodes.Stfld, typeof(BaseProxy).GetField("_methodResults", BindingFlags.Instance | BindingFlags.Public)); | |
il.Emit(OpCodes.Ret); | |
// 用來存放property的get,set function name,避免動態產生方法時重複產生 | |
List<string> omitMethods = new List<string>(); | |
// 取得要mock的type上所有的property,因為我們要建立一個動態的物件,其property與method的宣告,應該與要mock的type一樣 | |
foreach (var field in _proxyType.GetProperties()) | |
{ | |
// 為Dynamic$1234建立與mock的type相同的屬性 | |
CreateProperty(tb, field.Name, field.PropertyType); | |
omitMethods.Add("get_" + field.Name); | |
omitMethods.Add("set_" + field.Name); | |
} | |
// 取得要mock的type上所有的method,因為我們要建立一個動態的物件,其property與method的宣告,應該與要mock的type一樣 | |
foreach (var mtd in _proxyType.GetMethods()) | |
{ | |
// property已經建立過了,所以跳過已經建立過的property get/set function | |
if (omitMethods.IndexOf(mtd.Name) == -1) | |
CreateMethod(tb, mtd.Name, mtd.GetParameters().Select(a => a.ParameterType).ToArray(), mtd.ReturnType); | |
} | |
// 產生對應的動態物件型別 | |
_tpType = tb.CreateType(); | |
} | |
// 取得剛剛動態產生的type所定義的constructor,也就是constructor簽章為一個參數,其type為Dictionary<string,object>的那一個constructor | |
ConstructorInfo ci = _tpType.GetConstructor(new Type[] { typeof(Dictionary<string, object>) }); | |
// 透過reflection呼叫constructor,並傳入剛剛由外部設定的_methodResults | |
return ci.Invoke(new object[] { _methodResults }); | |
} | |
/// <summary> | |
/// 要產生type,要先產生assembly, 再產生module, 再產生type | |
/// </summary> | |
/// <returns></returns> | |
private TypeBuilder GetTypeBuilder() | |
{ | |
// type的名字要避免重複 | |
var typeSignature = "MyDynamicType_" + Guid.NewGuid().ToString().Replace("{", "").Replace('-', '$'); | |
var an = new AssemblyName(typeSignature); | |
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); | |
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); | |
// 動態產生基底型別為BaseProxy,並實作_proxyType這個interface的型別物件 | |
TypeBuilder tb = moduleBuilder.DefineType(typeSignature | |
, TypeAttributes.Public | | |
TypeAttributes.Class | | |
TypeAttributes.AutoClass | | |
TypeAttributes.AnsiClass | | |
TypeAttributes.BeforeFieldInit | | |
TypeAttributes.AutoLayout | |
, typeof(BaseProxy), new Type[] { _proxyType }); | |
return tb; | |
} | |
/// <summary> | |
/// 針對動態物件,使用IL建立其property | |
/// </summary> | |
/// <param name="tb">The tb.</param> | |
/// <param name="propertyName">Name of the property.</param> | |
/// <param name="propertyType">Type of the property.</param> | |
private void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType) | |
{ | |
// 要建立property,需要先建立對應的private field來存放get, set的值 | |
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); | |
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.SpecialName, propertyType, null); | |
// 產生property get的function,沒有參數,所以帶Type.EmptyTypes | |
MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | | |
MethodAttributes.HideBySig | MethodAttributes.Final | MethodAttributes.NewSlot | | |
MethodAttributes.Virtual, propertyType, Type.EmptyTypes); | |
ILGenerator getIl = getPropMthdBldr.GetILGenerator(); | |
// 回傳 field 的資料 | |
getIl.Emit(OpCodes.Ldarg_0); | |
getIl.Emit(OpCodes.Ldfld, fieldBuilder); | |
getIl.Emit(OpCodes.Ret); | |
// 產生property set的function,void type,所以帶null | |
MethodBuilder setPropMthdBldr = | |
tb.DefineMethod("set_" + propertyName, | |
MethodAttributes.Public | MethodAttributes.SpecialName | | |
MethodAttributes.HideBySig | MethodAttributes.Final | MethodAttributes.NewSlot | MethodAttributes.Virtual, | |
null, new[] { propertyType }); | |
ILGenerator setIl = setPropMthdBldr.GetILGenerator(); | |
Label modifyProperty = setIl.DefineLabel(); | |
Label exitSet = setIl.DefineLabel(); | |
// this._myfield = 第一個參數值(也就是Ldarg_1) | |
setIl.MarkLabel(modifyProperty); | |
setIl.Emit(OpCodes.Ldarg_0); | |
setIl.Emit(OpCodes.Ldarg_1); | |
setIl.Emit(OpCodes.Stfld, fieldBuilder); | |
setIl.Emit(OpCodes.Nop); | |
setIl.MarkLabel(exitSet); | |
setIl.Emit(OpCodes.Ret); | |
// 把Get/Set function塞到property中 | |
propertyBuilder.SetGetMethod(getPropMthdBldr); | |
propertyBuilder.SetSetMethod(setPropMthdBldr); | |
} | |
private void CreateMethod(TypeBuilder tb, string methodName, Type[] argsType, Type returnType) | |
{ | |
// 建立動態物件上的method,宣告成virtual | |
MethodBuilder mthdBldr = tb.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | | |
MethodAttributes.Virtual, returnType, argsType); | |
ILGenerator getIl = mthdBldr.GetILGenerator(); | |
// 因為mock與stub物件的目的,是為了模擬某一個物件的某一個方法被呼叫時,應該回傳指定的值,所以只針對非void做動作 | |
if (returnType != typeof(void)) | |
{ | |
// 建立動態物件上對應的這個方法,並轉呼叫BaseProxy.GetMethodResult(methodName); | |
getIl.Emit(OpCodes.Ldarg_0); | |
getIl.Emit(OpCodes.Ldstr, methodName); | |
getIl.Emit(OpCodes.Call, typeof(BaseProxy).GetMethod("GetMethodResult", BindingFlags.Public | BindingFlags.Instance)); | |
} | |
getIl.Emit(OpCodes.Ret); | |
} | |
} | |
} | |
// 動態產生的物件,程式碼大概如下所示: | |
public class Dynamic_1234 : BaseProxy, IPerson | |
{ | |
private string _Name; | |
private int _Age; | |
public Dynamic_1234(Dictionary<string, object> methodResults) | |
{ | |
this._methodResults = methodResults; | |
} | |
public string Name | |
{ | |
get | |
{ | |
return this._Name; | |
} | |
set | |
{ | |
this._Name = value; | |
} | |
} | |
public int Age | |
{ | |
get | |
{ | |
return this._Age; | |
} | |
set | |
{ | |
this._Age = value; | |
} | |
} | |
public virtual string Show() | |
{ | |
return Convert.ToString(this.GetMethodResult("Show")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment