Любой способ избежать встроенной оптимизации свойства в C#?

Итак, у меня есть класс PropertyBag, предназначенный для реализации INotifyPropertyChanged. Чтобы сделать этот код максимально чистым и избежать ошибок пользователя, я использую стек для получения имени свойства. Посмотрите, если имя свойства не совпадает с фактическим свойством, то у вас будет сбой, и я пытаюсь защититься от этого.

Итак, вот пример использования класса:

public class MyData : PropertyBag
{
    public MyData()
    {
        Foo = -1;
    }

    public int Foo
    {
        get { return GetProperty<int>(); }
        set { SetProperty(value); }
    }
}

Важный код для базового PropertyBag находится здесь:

public abstract class PropertyBag : INotifyPropertyChanged
{
    protected T GetProperty<T>()
    {
        string propertyName = PropertyName((new StackTrace()).GetFrame(1));
        if (propertyName == null)
            throw new ArgumentException("GetProperty must be called from a property");

        return GetValue<T>(propertyName);
    }

    protected void SetProperty<T>(T value)
    {
        string propertyName = PropertyName((new StackTrace()).GetFrame(1));
        if (propertyName == null)
            throw new ArgumentException("SetProperty must be called from a property");

        SetValue(propertyName, value);
    }

    private static string PropertyName(StackFrame frame)
    {
        if (frame == null) return null;
        if (!frame.GetMethod().Name.StartsWith("get_") &&
           !frame.GetMethod().Name.StartsWith("set_"))
            return null;

        return frame.GetMethod().Name.Substring(4);
    }
}

Итак, теперь, когда вы увидели мой код, я могу сообщить вам о проблеме... В некоторых случаях при сборке релиза установщик "Foo" в конструкторе "MyData", по-видимому, оптимизируется для встроенной функции SetProperty(-1). К сожалению, эта встроенная оптимизация не работает с моим методом SetProperty, потому что я больше не вызываю его из свойства! ПОТЕРПЕТЬ ПОРАЖЕНИЕ. Похоже, что я не могу полагаться на StackTrace таким образом.

Кто-нибудь может A: Найти лучший способ сделать это, но все же избегать передачи "Foo" в GetProperty и SetProperty?
B: Найдите способ сказать компилятору не оптимизировать в этом случае?

5 ответов

Решение

Использование стека здесь медленное и ненужное; Я бы просто использовал:

get { return GetProperty<int>("Foo"); }
set { SetProperty("Foo", value); }

(подсказка: я проделал большую работу с моделями пользовательских свойств; я знаю, что это хорошо работает...)

Другой альтернативой является ключ объекта (используйте сравнение ссылок для сравнения) - много ComponentModel работает так же, как и некоторые свойства в WF/WPF:

static readonly object FooKey = new object();
...
get { return GetProperty<int>(FooKey); }
set { SetProperty(FooKey, value); }

Конечно, вы можете объявить тип для ключей (с Name свойство), и используйте это:

static readonly PropertyKey FooKey = new PropertyKey("Foo");

так далее; однако, чтобы ответить на вопрос: пометьте его (но не делайте этого) с помощью:

[MethodImpl(MethodImplOptions.NoInlining)]

или же

[MethodImpl(MethodImplOptions.NoOptimization)]

или же

[MethodImpl(MethodImplAttributes.NoOptimization
    | MethodImplAttributes.NoInlining)]

Попробуйте новый атрибут [CallerMemberName].

Поместите его в параметр вашего метода ([CallerMemberName] callerName = null), и компилятор перепишет все вызовы вашего метода, чтобы автоматически передать имя вызывающего (ваши вызовы вообще не передают параметр).

Он не устраняет каких-либо оптимизаций, намного быстрее, чем лямбда-выражения или стеки, и работает в режиме Release.

PS если CallerMemberNameAttribute не существует в вашей версии фреймворка, просто определите его (пусто). Это языковая функция, а не базовая. Когда компилятор видит [CallerMemberNameAttribute] в параметре, он просто работает.

Использование стека не очень хорошая идея. Вы полагаетесь на внутреннюю реализацию компилятора для искусственного связывания в вашем пакете свойств со свойствами языка.

  1. имея требование добавить MethodImpl Атрибут делает использование вашей сумки свойств непрозрачным для других разработчиков.
  2. даже если сумка имеет MethodImpl атрибут, ничто не гарантирует вам, что это будет первый кадр в стеке вызовов. Вполне возможно, что сборка была оснащена или модифицирована для ввода вызовов между фактическим свойством и вызовом вашей сумки свойств. (Подумайте аспект программирования)
  3. Новые языки или даже будущая версия компилятора C# могут декорировать методы доступа к свойствам иначе, чем '_get' а также '_set'
  4. Построение стека вызовов является относительно медленной операцией, поскольку требует распаковки внутреннего сжатого стека и получения имени каждого типа и метода с использованием отражения.

Вам действительно нужно просто реализовать методы доступа к пакету свойств, чтобы получить параметр для идентификации свойства - либо строковое имя (например, Hastable), либо объект (например, пакет свойств зависимостей WPF).

Если вы хотите избежать жестко закодированных строк, вы можете использовать:

protected T GetProperty<T>(MethodBase getMethod)
{
    if (!getMethod.Name.StartsWith("get_")
    {
        throw new ArgumentException(
            "GetProperty must be called from a property");
    }
    return GetValue<T>(getMethod.Name.Substring(4));
}

Добавить больше проверки работоспособности, как вы считаете нужным

тогда собственность становится

public int Foo
{
    get { return GetProperty<int>(MethodInfo.GetCurrentMethod()); }     
}

наборы меняются таким же образом.

GetCurrentMethod() также проходит через стек, но делает это с помощью (внутреннего) неуправляемого вызова с использованием маркеров стека, поэтому будет работать и в режиме выпуска.

В качестве альтернативы для быстрого исправления [MethodImpl] с MethodImplAttributes.NoOptimization) или MethodImplAttributes.NoInlining также будет работать, хотя и с падением производительности (хотя, учитывая, что вы обходите кадр стека каждый раз, когда это попадание незначительно).

Еще один метод получения некоторого уровня проверки времени компиляции:

public class PropertyHelper<T>
{
    public PropertyInfo GetPropertyValue<TValue>(
        Expression<Func<T, TValue>> selector)
    {
        Expression body = selector;
        if (body is LambdaExpression)
        {
            body = ((LambdaExpression)body).Body;
        }
        switch (body.NodeType)
        {
            case ExpressionType.MemberAccess:
                return GetValue<TValue>(
                    ((PropertyInfo)((MemberExpression)body).Member).Name);
            default:
                throw new InvalidOperationException();
        }
    }
}

private static readonly PropertyHelper<Xxxx> propertyHelper 
    = new PropertyHelper<Xxxx>();

public int Foo
{
    get { return propertyHelper.GetPropertyValue(x => x.Foo); }     
}

где Xxxxx - это класс, в котором определено свойство. Если статическая природа вызывает проблемы (также возможно использование многопоточности или создание другого значения экземпляра).

Я должен отметить, что эти методы действительно ради интересов, я не предполагаю, что они являются хорошими общими методами для использования.

Вы можете попытаться создать файл шаблона T4, чтобы ваши свойства могли автоматически генерироваться с правильными именами свойств для методов GetProperty() и SetProperty().

T4: Набор инструментов преобразования текстовых шаблонов T4 ( Набор инструментов преобразования текстовых шаблонов) Генерация кода - Best Kept Visual Studio Secret

Другие вопросы по тегам