2017-07-20 102 views
1

我目前正在嘗試爲使用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; 
+0

'VAR二傳手= typeBuilder.DefineMethod(「SET_ 「+ prop.Name,MethodAttributes.Public,typeof(void),Type.EmptyTypes);'定義了一個不帶參數的方法(因爲你沒有提供參數的類型)。這是不正確的。另外,你能顯示完整的工作代碼嗎?包括實現你的界面的代碼,所以我們可以複製它? – Rob

+0

此外,測試出你的代碼似乎工作正常,在對setter進行更改之後 – Rob

+0

感謝您提供有關TypeParameter的提示。我添加了它們,但仍然拋出異常... – chrisih

回答

1

有點調試後,我發現您的問題。我注意到

wrapped.Text = ""正在進入TryCallMethodOneWay當它明確寫爲要求TrySetProperty

這是因爲foreach (var meth in typeof(TIf).GetMethods())將返回你的getter和setter方法。那是;你定義了兩次獲得者和接受者。

這是通過簡單的檢查解決:

var properties = typeof(TIf).GetProperties(); 
var propertyMethods = properties.SelectMany(p => new[] { p.GetGetMethod(), p.GetSetMethod() }).ToLookup(p => p); 

foreach (var meth in typeof(TIf).GetMethods()) 
{ 
    if (propertyMethods.Contains(meth)) 
     continue; 
    ... 
}    

現在,你也有你的實現方法爲Virtual標記,如果他們實現一個接口。所以,你需要更改代碼,如下所示:

var getter = typeBuilder.DefineMethod("get_" + prop.Name, MethodAttributes.Public | MethodAttributes.Virtual, prop.PropertyType, Type.EmptyTypes); 

而且

var setter = typeBuilder.DefineMethod("set_" + prop.Name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), new[] { prop.PropertyType }); 

而且你的代碼沒有問題,應該工作

+1

太棒了!現在工作就像一個魅力:-)非常感謝! – chrisih