我目前正在嘗試爲使用Reflection.Emit的接口創建一個「模擬」。 因此我創建了一個基類,用於所有動態生成的模擬。 對於接口中的屬性,我想在返回屬性值的基類中調用「Get」方法。Reflection.Emit InvalidProgramException
public class Mock
{
public static TIf Wrap<TIf>() where TIf : class
{
if (!typeof(TIf).IsInterface)
throw new Exception(typeof(TIf) + " is no interface");
var asmBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);
var modBuilder = asmBuilder.DefineDynamicModule("Mock", true);
var typename = "ImplOf" + typeof(TIf).Name.Replace(" ", "") + ".Mock";
var typeBuilder = modBuilder.DefineType(typename, TypeAttributes.Public, typeof(WrapperBase));
typeBuilder.AddInterfaceImplementation(typeof(TIf));
// methods
foreach (var meth in typeof(TIf).GetMethods())
{
var del = typeof(WrapperBase).GetMethod(meth.ReturnType != typeof(void) ? "TryCallMethod" : "TryCallMethodOneWay");
var mb = typeBuilder.DefineMethod(meth.Name, meth.Attributes^MethodAttributes.Abstract);
mb.SetParameters(meth.GetParameters().Select(p => p.ParameterType)?.ToArray());
mb.SetReturnType(meth.ReturnType);
var mbil = mb.GetILGenerator();
mbil.Emit(OpCodes.Ldarg_0);
mbil.Emit(OpCodes.Ldstr, meth.Name);
for (var i = 0; i < meth.GetParameters().Length; i++)
{
mbil.Emit(OpCodes.Ldarg, i + 1);
}
mbil.Emit(OpCodes.Call, del);
mbil.Emit(OpCodes.Ret);
}
// properties
foreach (var prop in typeof(TIf).GetProperties())
{
var propertyBuilder = typeBuilder.DefineProperty(prop.Name, prop.Attributes, prop.PropertyType, null);
if (prop.CanRead)
{
var getterDelegate = typeof(WrapperBase).GetMethod("TryGetProperty");
var getter = typeBuilder.DefineMethod("get_" + prop.Name, MethodAttributes.Public, prop.PropertyType, Type.EmptyTypes);
var gil = getter.GetILGenerator();
gil.Emit(OpCodes.Ldarg_0);
gil.Emit(OpCodes.Ldstr, prop.Name);
gil.Emit(OpCodes.Callvirt, getterDelegate);
gil.Emit(OpCodes.Castclass, prop.PropertyType);
gil.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getter);
}
if (prop.CanWrite)
{
var setterDelegate = typeof(WrapperBase).GetMethod("TrySetProperty");
var setter = typeBuilder.DefineMethod("set_" + prop.Name, MethodAttributes.Public, typeof(void), Type.EmptyTypes);
var sil = setter.GetILGenerator();
sil.Emit(OpCodes.Ldarg_0);
sil.Emit(OpCodes.Ldstr, prop.Name);
sil.Emit(OpCodes.Ldarg_1);
sil.Emit(OpCodes.Call, setterDelegate);
sil.Emit(OpCodes.Ret);
propertyBuilder.SetSetMethod(setter);
}
}
var retType = typeBuilder.CreateType();
return retType.GetConstructor(new Type[0]).Invoke(new object[0]) as TIf;
}
public abstract class WrapperBase
{
public event Func<string, object[], object> OnTryCallMethod;
public event Action<string, object[]> OnTryCallMethodOneWay;
public event Func<string, object> OnTryGetProperty;
public event Action<string, object> OnTrySetProperty;
/// <inheritdoc />
public object TryCallMethod(string name, object[] pars)
{
return OnTryCallMethod?.Invoke(name, pars);
}
/// <inheritdoc />
public void TryCallMethodOneWay(string name, object[] pars)
{
OnTryCallMethodOneWay?.Invoke(name, pars);
}
/// <inheritdoc />
public object TryGetProperty(string name)
{
return OnTryGetProperty?.Invoke(name);
}
/// <inheritdoc />
public void TrySetProperty(string name, object value)
{
OnTrySetProperty?.Invoke(name, value);
}
}
}
不幸的是,當試圖讀取「mocked」屬性時,我總是得到一個InvalidProgramException。 設置屬性(這將委託調用也一些基類方法)工作正常,方法調用相同。
爲了測試,我創建了一個非常簡單的接口:
public interface ITest
{
void Show(string text);
string Text { get; set; }
}
現在I'm調用像這樣的模擬:
var wrapped = Mock.Wrap<ITest>();
// ***************** works - EventHandler is called with correct parameters!
((Mock.WrapperBase)wrapped).OnTryCallMethodOneWay += (s, objects) => { };
wrapped.Show("sss");
// ***************** works - EventHandler is called with correct parameters!
wrapped.Text = "";
((Mock.WrapperBase)wrapped).OnTrySetProperty += (s, val) => { };
// ***************** does NOT work - getting InvalidProgramException
((Mock.WrapperBase)wrapped).OnTryGetProperty += s => "";
var t = wrapped.Text;
'VAR二傳手= typeBuilder.DefineMethod(「SET_ 「+ prop.Name,MethodAttributes.Public,typeof(void),Type.EmptyTypes);'定義了一個不帶參數的方法(因爲你沒有提供參數的類型)。這是不正確的。另外,你能顯示完整的工作代碼嗎?包括實現你的界面的代碼,所以我們可以複製它? – Rob
此外,測試出你的代碼似乎工作正常,在對setter進行更改之後 – Rob
感謝您提供有關TypeParameter的提示。我添加了它們,但仍然拋出異常... – chrisih