【问题标题】:How to get Getter backing field from PropertyInfo?如何从 PropertyInfo 获取 Getter 支持字段?
【发布时间】:2016-07-20 20:59:13
【问题描述】:

我有一堂课如下:

class Foo : PropertyChangedBase {
    private int _property;

    public int Property {
       get { return _property; }
       set { OnAssignPropertyChanged("Property", () => _property, value); }
}

PropertyChangedBase 使用以下方法实现INotifyPropertyChanged

    protected void OnAssignmentPropertyChanged<T>(string propertyName, Expression<Func<T>> fieldExpression, T value)
    {
        var get = fieldExpression.Compile();
        if (get().Equals(value))
        {
            return;
        }

        //  invoke set property method
        SetProperty(fieldExpression, value);
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private void SetProperty<T>(Expression<Func<T>> fieldExpression, T value)
    {
        if (fieldExpression == null)
        {
            throw new ArgumentNullException(nameof(fieldExpression));
        }

        var memberExpression = fieldExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            throw new ArgumentException("fieldExpression");
        }

        var field = memberExpression.Member as FieldInfo;
        if (field == null)
        {
            throw new ArgumentException("fieldExpression");
        }

        field.SetValue(this, value);
    }

我想打电话:

OnAssignPropertyChanged(() => Property, value);

唯一可行的方法是,如果我可以获得属性 getter 的支持字段,然后将其传递给 SetProperty。是否可以通过属性get方法获取FieldInfo或目标成员?

【问题讨论】:

    标签: c# reflection inotifypropertychanged getter propertyinfo


    【解决方案1】:

    作为一般答案,可以,至少在受控条件下是可以的。但是,您应该这样做的唯一情况是,当您完全确定自己在做什么并且只能在有限的支持下进行,因为有些情况是您无法处理的。

    在这里查看答案:Find all property references using reflection。目标有点不同,但查找字段引用的方法相似。由于那里的答案已经包含必要的代码,我将概述要走的路:

    .Net 中的所有元数据项都由令牌引用。要在方法中使用令牌,您必须解析 MethodBody(通过跳过所有您不会检查的内容)然后解析 resolve the found tokens in their module。请记住在从流中读取令牌以解析它们时使用BitConverter

    但现在是不利的一面;只有当您找到一个简单的 get 方法时,您才能真正安全地使用它来查找属性 getter 的支持字段,该方法具有明确定义的操作码序列,如 Ldfld、Ret 或类似的东西。也许您可以定义一些 C# 编译器将为简单和自动实现的属性发出的模式。如果您发现任何不同之处,则没有其他方法可以辞职并引发异常,因为 getter 可以包含任何代码。 与反射一样,仅使用白名单方法,检查您期望的条件并在任何其他情况下抛出异常,否则您迟早会遇到 NullReferenceException。

    如果这值得麻烦由您来决定,但通常您可以从 .Net 2.0 开始执行此操作,甚至不需要花哨的外部库。

    【讨论】:

    • 听起来很合理。对于从基类继承的类,该用例非常具体。根据我们的编码指南,我们可以强制执行使用规则。谢谢。
    【解决方案2】:

    不,一般情况下您不能。只需比较两个类:

    public class Test {
      private int _propA;
      private int _propB;
    
      public int PropA {get { return _propA; }}
      public int PropB {get { return _propB; }}
    } 
    
    public class TestSwapped {
      private int _propA;
      private int _propB;
    
      // please, notice swapped backing fields
      public int PropA {get { return _propB; }}
      public int PropB {get { return _propA; }}
    } 
    

    您将获得相同的 PropertyInfo[]FieldInfo[] 数组,但不同支持字段

    【讨论】:

    • 当然,您必须比较访问器的主体。这很难,因为任何 IL 代码都可能在其中。我在发布模式下构建了您的示例,并尝试了 typeof(TestSwapped).GetProperty("PropA").GetMethod.GetMethodBody().GetILAsByteArray() 和类似的 Test。它只是将“this”加载到堆栈中,从中加载相关字段(ldfld),然后返回。在 ldfld 指令的参数中可以看到差异。
    • @Jeppe Stig Nielsen: havig IL 代码,当然,我们可以在简单的情况下找出(或至少提出一个合理的猜测)支持字段(即@987654328 @);然而,在一般情况中,这是不可能(艾伦图灵停止定理修改)。例如。获取get {if (_propA &gt; 0) return _propB; else return _propB + 1;} 加载的第一个字段(取决于编译器)是_propA 支持字段是_propB
    • @DmitryBychenko:关于属性的好点。我的实现特定于简单的 getter:get { return _field; } ...实现我们的属性更改通知的模型或视图模型应该是简单的 getter。
    【解决方案3】:

    你不能。属性可以没有支持字段或支持字段集。 即使是 set 属性也可以完全没有支持字段。

    public Int32 Prop
    {
    set { Debug.WriteLine(value.ToString()); }
    get { return 1; }
    }
    

    您希望在 FieldInfo 中获得什么?

    【讨论】:

    • 在带有自动访问器的属性中,您可以通过反射获取自动支持字段:例如{ 得到;放; } 具有自动支持字段。因此,如果像我的示例中那样,getter 实际上返回了该字段,我假设您可以获得支持字段;
    • 好吧,据我所知你不能。
    • 它可能会,但它对编译器来说是一项巨大的工作。虽然简单的案例很简单,但案例很容易变得复杂。
    【解决方案4】:

    属性只是一对 set/get 方法或 mutators 的语法糖。作为一个方法,它们可以根据需要包含尽可能多的代码,包括只是空的,当然,从编译器的角度来看,不需要有一个支持字段。

    【讨论】:

      【解决方案5】:

      为了解决不同的问题,这里有一个简单案例的扩展方法 - 自动生成的支持字段,或只返回支持字段的 get:

      public static class MethodInfoExt {
          const int IL_ldarg0 = 0x02;
          const int IL_ldfld = 0x7B;
      
          public static FieldInfo FieldInfoFromGetAccessor(this MethodInfo getAccessorMI) {
              var body = getAccessorMI.GetMethodBody().GetILAsByteArray();
              if (body[0] == IL_ldarg0 && body[1] == IL_ldfld) {
                  var fieldToken = BitConverter.ToInt32(body, 2);
                  return getAccessorMI.DeclaringType.Module.ResolveField(fieldToken);
              }
              else
                  return default;
          }    
      }
      

      【讨论】:

        猜你喜欢
        • 2011-03-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-24
        • 2019-02-03
        相关资源
        最近更新 更多