Любой способ избежать встроенной оптимизации свойства в 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] в параметре, он просто работает.
Использование стека не очень хорошая идея. Вы полагаетесь на внутреннюю реализацию компилятора для искусственного связывания в вашем пакете свойств со свойствами языка.
- имея требование добавить
MethodImpl
Атрибут делает использование вашей сумки свойств непрозрачным для других разработчиков. - даже если сумка имеет
MethodImpl
атрибут, ничто не гарантирует вам, что это будет первый кадр в стеке вызовов. Вполне возможно, что сборка была оснащена или модифицирована для ввода вызовов между фактическим свойством и вызовом вашей сумки свойств. (Подумайте аспект программирования) - Новые языки или даже будущая версия компилятора C# могут декорировать методы доступа к свойствам иначе, чем
'_get'
а также'_set'
- Построение стека вызовов является относительно медленной операцией, поскольку требует распаковки внутреннего сжатого стека и получения имени каждого типа и метода с использованием отражения.
Вам действительно нужно просто реализовать методы доступа к пакету свойств, чтобы получить параметр для идентификации свойства - либо строковое имя (например, 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