Reflection.Emit InvalidProgramException

时间:2017-07-20 03:43:56

标签: c# reflection.emit

我正在尝试使用Reflection.Emit为界面创建“模拟”。 因此,我创建了一个基类,用于所有动态生成的模拟。 对于接口中的属性,我想在基类中调用一个返回属性值的“Get”方法。

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; }
}

现在我正在调用这样的模拟:

  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;

1 个答案:

答案 0 :(得分:1)

经过一些调试后,我发现了你的问题。我注意到了

wrapped.Text = ""明确写成TryCallMethodOneWay时,

TrySetProperty正在进入foreach (var meth in typeof(TIf).GetMethods())

这是因为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; ... } 将返回getter和setter方法。那是;你要两次定义getter和setter。

通过简单的检查解决了这个问题:

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}}

您的代码应该可以正常运行