代码之家  ›  专栏  ›  技术社区  ›  chrisih

反射发出无效程序异常

  •  1
  • chrisih  · 技术社区  · 7 年前

    对于接口中的属性,我想在返回属性值的基类中调用“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);
        }
      }
    }
    

    不幸的是,当我试图读取一个“模拟”属性时,我总是得到一个InvalidProgrammeException。 设置属性(也将调用委托给某些基类方法)效果很好,方法调用也是如此。

    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 回复  |  直到 7 年前
        1
  •  1
  •   Rob    7 年前

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

    wrapped.Text = "" TryCallMethodOneWay 当它清楚地写为呼叫时 TrySetProperty .

    foreach (var meth in typeof(TIf).GetMethods()) 将返回getter和setter方法。也就是说;您两次定义了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 });
    

    您的代码应该可以正常工作