Skip to content

Instantly share code, notes, and snippets.

@hatelove
Created December 4, 2012 11:53
Show Gist options
  • Save hatelove/4203022 to your computer and use it in GitHub Desktop.
Save hatelove/4203022 to your computer and use it in GitHub Desktop.
Dynamic Proxy by 黃忠成 - 程式碼註解 by 91
// 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