如何从 PropertyInfo 获取 Getter 支持字段?

How to get Getter backing field from PropertyInfo?

我有一个class如下:

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 或目标成员?

不,一般情况下你不能。比较两个 类:

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[] 数组,但 不同 支持字段

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

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

您希望在 FieldInfo 中得到什么?

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

作为一般性回答,是的,至少在受控条件下可以。但唯一应该这样做的情况是当你绝对确定你在做什么并且只有有限的支持时,因为会有你无法处理的情况。

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

.Net 中的所有元数据项都由令牌引用。要在方法中使用令牌,您必须在从流中读取令牌以解析它们时解析 MethodBody (by skipping all the things you wont inspect) and then resolve the found tokens in their module. Remember to use the BitConverter

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

是否值得麻烦由您来决定,但通常您可以从 .Net 2.0 开始这样做,甚至不需要花哨的外部库。

为了解决另一个问题,这里有一个简单案例的扩展方法 - 一个自动生成的支持字段,或者只是 returns 一个支持字段:

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