Как получить поле поддержки Getter из PropertyInfo?

У меня есть класс следующим образом:

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

Это сработает только в том случае, если я смогу получить поле поддержки для получателя свойств, а затем передать его SetProperty. Можно ли получить FieldInfo или целевой элемент из метода получения свойства?


person IAbstract    schedule 20.07.2016    source источник


Ответы (5)


Как правило, да, вы можете это сделать, по крайней мере, в контролируемых условиях. Но единственный случай, когда вы должны это делать, — это когда вы абсолютно уверены в том, что делаете, и только с ограниченной поддержкой, потому что будут случаи, с которыми вы не сможете справиться.

Взгляните на ответ здесь: Найти все ссылки на свойства с помощью отражения. Цель немного другая, но подход к поиску ссылок на поля аналогичен. Поскольку ответ там уже включает необходимый код, я просто обрисую путь:

На все элементы метаданных в .Net ссылаются токены. Чтобы получить токены, используемые внутри метода, вы должны проанализировать MethodBody (пропустив все, что вы не будете проверять), а затем разрешить найденные токены в своем модуле. Не забудьте использовать BitConverter при чтении токенов из потока для их разрешения.

Но теперь к обратной стороне; единственный раз, когда вы можете действительно безопасно использовать это, чтобы найти резервные поля получателя свойств, - это когда вы найдете простой метод get с четко определенной последовательностью кода операции, такой как Ldfld, Ret или что-то в этом роде. Возможно, вы сможете определить несколько шаблонов, которые компилятор C# будет выдавать для простых и автоматически реализуемых свойств. Если вы обнаружите что-то другое, нет другого пути, как уйти в отставку и выдать исключение, потому что геттер может содержать любой код. Как и всегда с отражением, используйте только подходы из белого списка, проверяйте ожидаемые условия и выбрасывайте исключения в любом другом случае, иначе рано или поздно вы столкнетесь с NullReferenceException.

Стоит ли это того, чтобы решать вам, но в целом вы могли бы сделать это, начиная с .Net 2.0, и вам даже не нужна навороченная внешняя библиотека.

person thehennyy    schedule 21.07.2016
comment
Звучит разумно. Вариант использования очень специфичен для классов, которые наследуются от базового класса. Основываясь на нашем руководстве по кодированию, мы можем обеспечить соблюдение правил использования. Спасибо. - person IAbstract; 21.07.2016

Нет, в общем случае вы не можете. Просто сравните два класса:

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[], но разные вспомогательные поля

person Dmitry Bychenko    schedule 20.07.2016
comment
Разумеется, вам придется сравнивать тела аксессоров. Это сложно, так как там может быть любой код IL. Я построил ваш пример в режиме Release и попробовал typeof(TestSwapped).GetProperty("PropA").GetMethod.GetMethodBody().GetILAsByteArray() и подобное для Test. Он просто загружает это в стек, загружает оттуда соответствующее поле (ldfld) и возвращает его. Разница была замечена в аргументе инструкции ldfld. - person Jeppe Stig Nielsen; 21.07.2016
comment
@Jeppe Stig Nielsen: имея код IL, конечно, мы можем узнать (или, по крайней мере, предложить разумное предположение) резервное поле в простых случаях (например, return _propB;); однако в общем случае это невозможно (модификация теоремы об остановке Алана Тьюринга). Например. получить get {if (_propA > 0) return _propB; else return _propB + 1;} первое загруженное поле (зависит от компилятора) _propA вспомогательное поле _propB - person Dmitry Bychenko; 21.07.2016
comment
@DmitryBychenko: отличное замечание по свойствам. Моя реализация специфична для простых геттеров: get { return _field; } ...Модели или ViewModels, реализующие наше уведомление об изменении свойства, должны быть простыми геттерами. - person IAbstract; 21.07.2016

Вы не можете. Свойство не может иметь резервных полей или наборов резервных полей. Даже установленное свойство может вообще не иметь резервных полей.

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

Что вы ожидаете получить в FieldInfo?

person Sergey.quixoticaxis.Ivanov    schedule 20.07.2016
comment
В свойствах с автоматическими доступами вы можете получить поля автоматической поддержки через отражение: например. { получить; установлен; } имеет автоматически поддерживающие поля. Итак, я бы предположил, что вы можете получить вспомогательное поле, если, как в моем примере, геттер действительно возвращает поле; - person IAbstract; 21.07.2016
comment
Ну нельзя, насколько я знаю. - person Sergey.quixoticaxis.Ivanov; 21.07.2016
comment
Возможно, это возможно, но это огромная работа для компилятора. Хотя простые случаи понятны, случаи могут легко усложняться. - person Sergey.quixoticaxis.Ivanov; 21.07.2016

Свойство — это просто синтаксический сахар для пары методов set/get или мутаторов. Будучи методом, они могут содержать столько кода, сколько необходимо, в том числе быть просто пустыми, и, конечно же, нет необходимости иметь резервное поле с точки зрения компилятора.

person Alex Seleznyov    schedule 20.07.2016

В поисках этого для другого вопроса, вот метод расширения для простых случаев - автоматически сгенерированное поле поддержки или получение, которое просто возвращает поле поддержки:

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;
    }    
}
person NetMage    schedule 17.07.2019