C#,如何在 mvvmlight 风格中使用 Reflection.Emit 属性 getter setter 动态创建

C#, How to dynamically create with Reflection.Emit property getter setter in mvvmlight style

如何创建 属性 如下 Reflection.Emit

    private string _Name;

    public override string Name{ get => _Name; set => Set(ref _Name, value); }

我用 Reflection.Emmit

试过了
    private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
    {
      // propertyName = "Name";
      // propertyType = typeof(string);
      // private string _Name;
      FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

      // public string Name {get; set;}
      PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
      // get;
      MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
      ILGenerator getIl = getPropMthdBldr.GetILGenerator();
      // get => _Name
      getIl.Emit(OpCodes.Ldarg_0);
      getIl.Emit(OpCodes.Ldfld, fieldBuilder);
      getIl.Emit(OpCodes.Ret);

      // set;
      MethodBuilder setPropMthdBldr =
          tb.DefineMethod("set_" + propertyName,
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig,
            null, new[] { propertyType });

      ILGenerator setIl = setPropMthdBldr.GetILGenerator();


      var MvvmLightSetMethod = typeof(ObservableObject)
                               .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                               .Where(m => m.Name == "Set").ToList()[2];

      // set => Set(ref _Name, value);
      setIl.Emit(OpCodes.Mkrefany, fieldBuilder);
      setIl.Emit(OpCodes.Ldarg_0);
      setIl.Emit(OpCodes.Ldarg_1);
      setIl.Emit(OpCodes.Stfld, propertyName);
      setIl.Emit(OpCodes.Call, MvvmLightSetMethod);
      setIl.Emit(OpCodes.Ret);

      // public string Name {get; set;}
      propertyBuilder.SetGetMethod(getPropMthdBldr);
      propertyBuilder.SetSetMethod(setPropMthdBldr);
    }

我的问题

如果我 运行 此代码并调用 setter 我在 set_Name(string)

中得到 "Invalid class token exception"

你调用 Set<T> 的 IL 是完全错误的。

Set<T> 具有以下签名:

protected bool Set<T>(
    ref T field,
    T newValue,
    [CallerMemberName] string propertyName = null)

请记住,Type.GetMethods 编写的方法 return 的顺序是未定义的,可以更改。 不要 简单地对此进行索引 - 确保通过指定其签名来获得正确的方法。它也是一种通用方法,因此您需要调用 MakeGenericMethod.

我也不知道你想用 Mkrefany 做什么。看起来您正在尝试执行 ref this.field,但已通过 Ldflda.

完成

最后,Set<T> return 是一个值,您需要先将其弹出 return。

把所有这些放在一起,你会得到这个:

var setMethod = (from method in typeof(ObservableObject).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                 where method.Name == "Set"
                 let parameters = method.GetParameters()
                 where parameters.Length == 3 &&
                 parameters[0].ParameterType.IsByRef &&
                 parameters[0].ParameterType.ContainsGenericParameters &&
                 parameters[1].ParameterType.ContainsGenericParameters &&
                 parameters[2].ParameterType == typeof(string)
                 select method).Single().MakeGenericMethod(propertyType);

// set => Set(ref _Name, value);
setIl.Emit(OpCodes.Ldarg_0); // Load 'this' -- we'll use it when calling Set later
// Stack: [this]
setIl.Emit(OpCodes.Ldarg_0); // Load 'this' again, for accessing 'this.field'
// Stack: [this, this]
setIl.Emit(OpCodes.Ldflda, fieldBuilder); // Load the address of 'this.field'
// Stack: [this, ref this.field]
setIl.Emit(OpCodes.Ldarg_1); // Load 'value'
// Stack: [this, ref this.field, value]
setIl.Emit(OpCodes.Ldstr, propertyName); // Load 'propertyName'
// Stack: [this, ref this.field, value, propertyName]
setIl.Emit(OpCodes.Call, setMethod);
// Stack: [bool]
setIl.Emit(OpCodes.Pop); // Pop the returned value
// Stack: []
setIl.Emit(OpCodes.Ret);

SharpLab 是编写 IL 的无价之宝。