Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, SiAVoL, Вы писали:
SAV>>Мне очень давно не нравилось использование имен в виде строк в биндинге или, например, при реализации интерфейса INotifyPropertyChanged. Очень ненадежно и код так и норовит сломаться при рефакторинге. Вчера мне пришло в голову решение на основе Expression. Наверняка кто-то уже находил такое решение или даже использует его, потому что решение лежит на поверхности. Но мне такой подход до сих пор не попадался, поэтому надеюсь что кому-то мой пост будет интересен. SAV>>Итак приведу небольшой класс, глядя на который будет ясно что я имею в виду: S>Правильной дорогой идете, товарищи. Аналогичные трюки применяют в ASP.NET MVC Framework. S>Я бы это назвал "использование лямбд для записи ссылок на мемберы". S>Общий принцип: делаем лямбду, которая никогда не вызывается. Вместо этого она используется для интроспекции и получения побочных эффектов.
S>А теперь попробуем выполнить некоторый оверклокинг: S>
Мне очень давно не нравилось использование имен в виде строк в биндинге или, например, при реализации интерфейса INotifyPropertyChanged. Очень ненадежно и код так и норовит сломаться при рефакторинге. Вчера мне пришло в голову решение на основе Expression. Наверняка кто-то уже находил такое решение или даже использует его, потому что решение лежит на поверхности. Но мне такой подход до сих пор не попадался, поэтому надеюсь что кому-то мой пост будет интересен.
Итак приведу небольшой класс, глядя на который будет ясно что я имею в виду:
class A1 : INotifyPropertyChanged
{
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
InvokePropertyChanged(() => MyProperty);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void InvokePropertyChanged<T>(Expression<Func<T>> property)
{
PropertyChangedEventHandler Handler = PropertyChanged;
if (Handler != null)
{
MemberExpression expression = (MemberExpression) property.Body;
Handler(this, new PropertyChangedEventArgs(expression.Member.Name));
}
}
}
При таком подходе можно быть спокойным во время рефакторинга.
Конечно же меня заинтересовало насколько просядет производительность при таком коде
Здравствуйте, SiAVoL, Вы писали:
SAV>Мне очень давно не нравилось использование имен в виде строк в биндинге или, например, при реализации интерфейса INotifyPropertyChanged. Очень ненадежно и код так и норовит сломаться при рефакторинге.
Вот это и надо лечить, объявляя константы с именами свойств, а не изобретать квадратные колёса.
SAV>Вчера мне пришло в голову решение на основе Expression.
Потеряешь на встраивании. Проверять надо на релизе при запуске не из под отладчика.
Help will always be given at Hogwarts to those who ask for it.
мне кажется очень красивые колеса, элегантные. Возможно ездить на них и медленнее, но как средство от головной боли очень даже — какая разница какие колеса ;)
Здравствуйте, SiAVoL, Вы писали:
SAV>Видно, что "новый" подход примерно в 20 раз медленнее классического. Но в принципе для любителей "не экономить на спичках", чем не вариант?
Сам такой тоже писал пол года назад
Учитывая, что подписчик на данное свойство обычно использует reflection чтобы вытащить новое значение свойства — разница в скорости совсем будет не заметна
Можно ещё лямбда-выражения сделать статическими членами класса, а в хелпере использовать хеш-таблицу для хранения уже вычисленных имён свойств.
Но я думаю, что в типичных сценариях эта дополнительная писанина/оптимизация себя не оправдает.
Здравствуйте, SiAVoL, Вы писали:
SAV>Мне очень давно не нравилось использование имен в виде строк в биндинге или, например, при реализации интерфейса INotifyPropertyChanged. Очень ненадежно и код так и норовит сломаться при рефакторинге. Вчера мне пришло в голову решение на основе Expression. Наверняка кто-то уже находил такое решение или даже использует его, потому что решение лежит на поверхности. Но мне такой подход до сих пор не попадался, поэтому надеюсь что кому-то мой пост будет интересен. SAV>Итак приведу небольшой класс, глядя на который будет ясно что я имею в виду:
Правильной дорогой идете, товарищи. Аналогичные трюки применяют в ASP.NET MVC Framework.
Я бы это назвал "использование лямбд для записи ссылок на мемберы".
Общий принцип: делаем лямбду, которая никогда не вызывается. Вместо этого она используется для интроспекции и получения побочных эффектов.
А теперь попробуем выполнить некоторый оверклокинг:
private static Dictionary<Expression, PropertyChangedEventArgs> _propertyArgs = new Dictionary<Expression, PropertyChangedEventArgs>();
private void InvokePropertyChanged<T>(Expression<Func<T>> property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null)
return;
PropertyChangedEventArgs args;
if (!_propertyArgs.TryGetValue(property, out args))
{
MemberExpression expression = (MemberExpression) property.Body;
args = new PropertyChangedEventArgs(expression.Member.Name);
_propertyArgs[property] = args;
}
handler(this, args);
}
Померишь скорость?
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, adanov, Вы писали:
A>Здравствуйте, Curufinwe, Вы писали:
C>>Что же Вы два разных экземпляра сравниваете? Сделайте для каждого проперти один раз выражение и поместите в статичечкую переменную.
A>Дык как же в статическую переменную можно поместить ссылку на выражение с нестатическим свойством? A>плавали, влипли
Здравствуйте, _FRED_, Вы писали:
_FR>Вот это и надо лечить, объявляя константы с именами свойств, а не изобретать квадратные колёса.
А как константы помогут при рефакторинге? И или предлагается сделать public константу с именем свойства, которую везде и использовать, а при рефакторинге не забывать изменить эту константу руками? По мне эти колеса нисколько не круглее.
_FR>Проверять надо на релизе при запуске не из под отладчика.
я и так проверял на релизе не из-под отладчика.
Здравствуйте, arkhivania, Вы писали:
A>Если вы используете данное в WPF, то в sp1 для .Net 3.5 заработало долгожданное event EventHandler PropertyNameChanged.
Это то, что давно работает в WinForms? Т.е. выделенное это имя конкретного свойства? Если да, то мне подход с INotifyPropertyChanged нравится даже больше. Не надо плодить кучу событий, поэтому код более краткий. К тому же наличие такого события опять же не поможет при рефакторинге, событие все равно надо будет не забыть переименовать руками.
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re: Удобное получение имени свойства
От:
Аноним
Дата:
18.08.08 12:27
Оценка:
Здравствуйте, SiAVoL, Вы писали:
Предлагаю следующее решение, почти без затрат времени, и писанины меньше:
class A3 : INotifyPropertyChanged
{
private int _myProperty;
public int MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
PropertyChanged.Fire(this);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
static class Utils
{
static System.Collections.Generic.Dictionary<Type, string> dict = new System.Collections.Generic.Dictionary<Type, string>();
public static void Fire(this PropertyChangedEventHandler handler, INotifyPropertyChanged obj)
{
if (handler != null)
{
string propertyName;
if (!dict.TryGetValue(obj.GetType(), out propertyName))
{
dict[obj.GetType()] = propertyName =
(new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod().Name.Substring(4);
}
handler(obj, new PropertyChangedEventArgs(propertyName));
}
}
}
Здравствуйте, <Аноним>, Вы писали:
А>Предлагаю следующее решение, почти без затрат времени, и писанины меньше:
либо я чего-то не понял, либо предполагается, что у каждого класса будет не более одного свойства с поддержкой уведомления об изменениях.
Здравствуйте, SiAVoL, Вы писали:
SAV>Здравствуйте, <Аноним>, Вы писали:
А>>Предлагаю следующее решение, почти без затрат времени, и писанины меньше: SAV>либо я чего-то не понял
Видимо вы не поняли, имя свойства берется из стека.
А про то что INotifyPropertyChanged типа хорошее решение (меньше кода и не нужно плодить ивентов), думаю что с этим согласится меньше 1% проектировщиков.
Здравствуйте, SiAVoL, Вы писали:
SAV>... у каждого класса будет не более одного свойства с поддержкой уведомления об изменениях.
увы это так
будем искать другое решение
Здравствуйте, Аноним, Вы писали:
А>Предлагаю следующее решение, почти без затрат времени, и писанины меньше: А>Вот мои результаты тестирования: А>Получается чуть медленнее оригинального (но с пустыми обработчиками !!!!). А>Но если свойство имеет тип System.Int32, результаты значительно отличаются: А>т.е. улучшение в 100 раз при безопасном рефакторинге
А сколько получится, ежели проводить измерения при глубине стека вызовов в районе 30+ ?
Здравствуйте, arkhivania, Вы писали:
A>Видимо вы не поняли, имя свойства берется из стека.
я все понял. Только суть не в этом, там словарь с ключем по типу. Т.е. вызвали метод из одного свойства, в словарь записался тип и имя свойства. Вызвали из другого свойства, полетело событие с именем первого свойства. Мне кажется, что это неправильное поведение. Впрочем автор со мной уже согласился уже согласился
A>А про то что INotifyPropertyChanged типа хорошее решение (меньше кода и не нужно плодить ивентов), думаю что с этим согласится меньше 1% проектировщиков.
А чем решение плохо? У него есть своя обширная ниша — бизнес-объекты. Для ивента на каждое свойство нужно:
— во-первых писать эти ивенты. Да, можно сделать снипеты, шаблоны и пр.
— во-вторых на каждое свойство надо держать поле-делегат. А они память кушают. А подписываться на все события нужно далеко не всегда. Да, можно переопределить add/remove для события.
Все решаемо, но для чего плодить сложности если для классов бизнес-объектов INotifyPropertyChanged нужен в основном для связи с UI?
Для компонентов, не спорю, часто отдельные события предпочтительнее.
Вообще если нужно сверхскоростное решение, то придумать такое могу ), правда с небольшими ограничениями.
1) Свойство помечается неким своим аттрибутом.
2) Далее в сеттере используете например свой способ (или любой "медленный")
-- до этого места всё как обычно, работаете, отлаживаете, а вот в релизе
3) Пишете утилиту, которая при помощи Cecil заменяет медленный способ на стандартный у всех свойств помеченных вашим аттрибутом.
Здравствуйте, arkhivania, Вы писали:
A>3) Пишете утилиту, которая при помощи Cecil заменяет медленный способ на стандартный у всех свойств помеченных вашим аттрибутом.
Здравствуйте, arkhivania, Вы писали:
A>интересно, только отлаживаться так не очень удобно я так понимаю. Но вообще идея такая же.
Отлаживается, на удивление, гладко.
При трассировке точка выполнения заходит везде где нужно, без глюков.
Народ, не слыхавший об инжекции, впадает в ступор.
Здравствуйте, drol, Вы писали:
D>А сколько получится, ежели проводить измерения при глубине стека вызовов в районе 30+ ?
без кэширования — на порядок медленнее чем у SiAVoL
с кэшированием смысла нет (работает только для одного свойства )
Как дети малые, честное слово
От рутины спасает аспектно-ориентированное программирование.
Это не фрагмент из реального проекта (там почти то же, только посложнее), а из головы. так что sorry за ашыпки:
C# 2.0
Допустим, базовый класс всех сущностей:
public abstract class Entity : INotifyPropertyEvent
{
...
void OnPropertyChanged(string propertyName);
}
Его наследник:
public class Person : Entity
{
private string name;
public virtual string Name
{
get { return name; }
set { name = value; }
}
}
Аспект, срабатывающий при вызове любого виртуального (или интерфейсного, или переопределенного) метода или свойства.
internal class PropertyCyhangedInterceptor : IMethodInterceptor
{
public object Invoke(Invocation invocation)
{
object result = invocation.Proceed();
MethodInfo method = invocation.Method;
if (IsPropertySetter(method))
{
Entity entity = invocation.Target;
string propertyName = GetPropertyName(method);
entity.OnPropertyChanged(propertyName);
}
}
}
Репозиторий, из которого извлекаются экземпляры нашей сущности, уже с внедренными аспектами.
Person person = entityManager.Find<Person>(id);
person.Name = "Vassily";
В момент присвоения происходит генерация события из интерфейса INotifyPropertyEvent.
Скорость — где-то 90% от номинала. Хотя... может я применял какие-то оптимизации... не помню. Но не важно, смысл не меняется.
Давным давно используется, проблем никаких, рекомендую. Нет, вру: иногда забывают объявить свойство виртуальным, но это лечится.
На опушке за околицей мужики строили коровник.
Работали споро и весело. Получалось х**во.
Здравствуйте, Sinclair, Вы писали:
S>А теперь попробуем выполнить некоторый оверклокинг: S>Померишь скорость?
померял. В твоем варианте сильно медленнее даже моего. Все из-за того, что Expression не умеют сравниваться. Сходу я нормального метода сравнения не обнаружил. Поэтому сделал "наивным" методом
string propertyString = property.ToString();if (!_propertyArgs.TryGetValue(propertyString, out args))
{
MemberExpression expression = (MemberExpression)property.Body;
args = new PropertyChangedEventArgs(expression.Member.Name);
_propertyArgs[propertyString] = args;
}
Так кэширование действительно работает. Но толку от него немного:
Здравствуйте, SiAVoL, Вы писали: SAV>померял. В твоем варианте сильно медленнее даже моего. Все из-за того, что Expression не умеют сравниваться.
Не может такого быть!
Как это не умеют сравниваться? Они же ref-типы, значит умеют сравниваться по identity.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, SiAVoL, Вы писали: S>>Как это не умеют сравниваться? Они же ref-типы, значит умеют сравниваться по identity. SAV>не могу знать!
Ты страдаешь ерундой. Какая тебе разница, что вернет Equals?
Ты скомпилируй и замерь тот код, который я привел. Без улучшений.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Ты страдаешь ерундой. Какая тебе разница, что вернет Equals? S>Ты скомпилируй и замерь тот код, который я привел. Без улучшений.
хорошо, вот класс, сделанный из твоего кода
class A3 : INotifyPropertyChanged
{
private static readonly Dictionary<Expression, PropertyChangedEventArgs> _propertyArgs = new Dictionary<Expression, PropertyChangedEventArgs>();
private string _myProperty;
public event PropertyChangedEventHandler PropertyChanged;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
InvokePropertyChanged(() => MyProperty);
}
}
private void InvokePropertyChanged<T>(Expression<Func<T>> property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null)
return;
PropertyChangedEventArgs args;
if (!_propertyArgs.TryGetValue(property, out args))
{
MemberExpression expression = (MemberExpression)property.Body;
args = new PropertyChangedEventArgs(expression.Member.Name);
_propertyArgs[property] = args;
}
handler(this, args);
}
}
Здравствуйте, SiAVoL, Вы писали:
SAV>Вот еще один маленький фрагмент, для проверки работает ли кэширование
Ага, интересно. Я полагал, что компилятор построит дерево статически и будет его повторно использовать в вызове.
Похоже, он так не сделал. Тогда простой способ — в том, чтобы принудительно применять один и тот же объект, но это будет некрасиво.
Значит, надо думать еще.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Curufinwe, Вы писали:
C>Что же Вы два разных экземпляра сравниваете? Сделайте для каждого проперти один раз выражение и поместите в статичечкую переменную.
Дык как же в статическую переменную можно поместить ссылку на выражение с нестатическим свойством?
плавали, влипли
Здравствуйте, akarinsky, Вы писали:
A>Давным давно используется, проблем никаких, рекомендую. Нет, вру: иногда забывают объявить свойство виртуальным, но это лечится.
При этом появляется другие проблемы — навскидку, проконтролировать, что все объекты создаются через фабрику, нужно помнить, что имя класса будет оличаться, делать все свойства виртуальными.
Здравствуйте, akarinsky, Вы писали:
A>Здравствуйте, SiAVoL, Вы писали:
A>Как дети малые, честное слово A>От рутины спасает аспектно-ориентированное программирование. A>Это не фрагмент из реального проекта (там почти то же, только посложнее), а из головы. так что sorry за ашыпки:
я тебе тоже могу привести пример
public abstract class Person : EditableObject<Person>
{
public abstract string Name { get; set; }
public abstract int Age { get; set; }
}
...
Person person = Person.CreateInstance();
и получить класс с поддержкой INotifyPropertyChanged, отмены или принятия изменений, флагом IsDirty, валидацией на атрибутах, также через атрибуты легко привинчиваются аспекты типа логирования вызовов, кэширования и прочее. Все что нужно для этого щастья это сделать линк на BLToolKit.
Только вот не везде такие решения нужны и оправданы. А красиво жить хочется всегда.
Здравствуйте, akarinsky, Вы писали:
A>Здравствуйте, SiAVoL, Вы писали:
A>Как дети малые, честное слово A>От рутины спасает аспектно-ориентированное программирование. A>Это не фрагмент из реального проекта (там почти то же, только посложнее), а из головы. так что sorry за ашыпки:
я тебе тоже могу привести пример
public abstract class Person : EditableObject<Person>
{
public abstract string Name { get; set; }
public abstract int Age { get; set; }
}
...
Person person = Person.CreateInstance();
и получить класс с поддержкой INotifyPropertyChanged, отмены или принятия изменений, флагом IsDirty, валидацией на атрибутах, также через атрибуты легко привинчиваются аспекты типа логирования вызовов, кэширования и прочее. Все что нужно для этого щастья это сделать линк на BLToolKit.
Только вот не везде такие решения нужны и оправданы. А красиво жить хочется всегда.