Re[7]: Удобное получение имени свойства
От: arkhivania  
Дата: 18.08.08 14:24
Оценка:
Здравствуйте, Curufinwe, Вы писали:

C>Раз уж пошло такое дело, то используя PostSharp например, можно весь интерфейс INotifyPropertyChanged реализовать одним атрибутом на классе:

C>http://www.acceptedeclectic.com/2008/01/postsharp-part-2-databinding-support.html

интересно, только отлаживаться так не очень удобно я так понимаю. Но вообще идея такая же.
Re[8]: Удобное получение имени свойства
От: adanov  
Дата: 18.08.08 14:42
Оценка:
Здравствуйте, arkhivania, Вы писали:

A>интересно, только отлаживаться так не очень удобно я так понимаю. Но вообще идея такая же.


Отлаживается, на удивление, гладко.
При трассировке точка выполнения заходит везде где нужно, без глюков.
Народ, не слыхавший об инжекции, впадает в ступор.
Re[3]: Удобное получение имени свойства
От: adanov  
Дата: 18.08.08 14:51
Оценка:
Здравствуйте, drol, Вы писали:

D>А сколько получится, ежели проводить измерения при глубине стека вызовов в районе 30+ ?

без кэширования — на порядок медленнее чем у SiAVoL
с кэшированием смысла нет (работает только для одного свойства )
Re: Удобное получение имени свойства
От: akarinsky Россия  
Дата: 18.08.08 22:18
Оценка:
Здравствуйте, 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% от номинала. Хотя... может я применял какие-то оптимизации... не помню. Но не важно, смысл не меняется.

Давным давно используется, проблем никаких, рекомендую. Нет, вру: иногда забывают объявить свойство виртуальным, но это лечится.
На опушке за околицей мужики строили коровник.
Работали споро и весело. Получалось х**во.
Re: Удобное получение имени свойства
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.08.08 03:37
Оценка: 3 (1)
Здравствуйте, 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>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 19.08.08 05:21
Оценка:
Здравствуйте, 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;
}

Так кэширование действительно работает. Но толку от него немного:
sinclair:   0,7320  new:   0,5772  old:   0,0244
sinclair:   0,7255  new:   0,5547  old:   0,0241
sinclair:   0,7221  new:   0,5554  old:   0,0239
sinclair:   0,8677  new:   0,5609  old:   0,0237
sinclair:   0,7169  new:   0,5530  old:   0,0240
sinclair:   0,7184  new:   0,5569  old:   0,0239
sinclair:   0,7163  new:   0,5544  old:   0,0236
sinclair:   0,7282  new:   0,5525  old:   0,0242
sinclair:   0,7202  new:   0,5571  old:   0,0245
sinclair:   0,7211  new:   0,5550  old:   0,0243

Надо искать нормальный метод сравнения Expression...
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re[3]: Удобное получение имени свойства
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.08.08 05:43
Оценка:
Здравствуйте, SiAVoL, Вы писали:
SAV>померял. В твоем варианте сильно медленнее даже моего. Все из-за того, что Expression не умеют сравниваться.
Не может такого быть!
Как это не умеют сравниваться? Они же ref-типы, значит умеют сравниваться по identity.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 19.08.08 06:02
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

S>Как это не умеют сравниваться? Они же ref-типы, значит умеют сравниваться по identity.

не могу знать!
using System;
using System.Linq.Expressions;

namespace ExpresionEquals
{
    class Program
    {
        static void Main()
        {
            A a = new A();
            bool equals = Compare(() => a.MyProperty, () => a.MyProperty);
            Console.WriteLine(equals ? "equals" : "not equals");
        }

        static bool Compare<T>(Expression<Func<T>> expr1, Expression<Func<T>> expr2)
        {
            return Equals(expr1, expr2);
        }
    }

    class A
    {
        public string MyProperty { get; set; }
    }
}

выводит выделенное
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re[5]: Удобное получение имени свойства
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.08.08 06:17
Оценка:
Здравствуйте, SiAVoL, Вы писали:
S>>Как это не умеют сравниваться? Они же ref-типы, значит умеют сравниваться по identity.
SAV>не могу знать!
Ты страдаешь ерундой. Какая тебе разница, что вернет Equals?
Ты скомпилируй и замерь тот код, который я привел. Без улучшений.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: Удобное получение имени свойства
От: Curufinwe Украина  
Дата: 19.08.08 06:22
Оценка:
Здравствуйте, SiAVoL, Вы писали:

SAV>не могу знать!

SAV>
SAV>using System;
SAV>using System.Linq.Expressions;

SAV>namespace ExpresionEquals
SAV>{
SAV>    class Program
SAV>    {
SAV>        static void Main()
SAV>        {
SAV>            A a = new A();
SAV>            bool equals = Compare(() => a.MyProperty, () => a.MyProperty);
SAV>            Console.WriteLine(equals ? "equals" : "not equals");
SAV>        }

SAV>        static bool Compare<T>(Expression<Func<T>> expr1, Expression<Func<T>> expr2)
SAV>        {
SAV>            return Equals(expr1, expr2);
SAV>        }
SAV>    }

SAV>    class A
SAV>    {
SAV>        public string MyProperty { get; set; }
SAV>    }
SAV>}
SAV>

SAV>выводит выделенное

Что же Вы два разных экземпляра сравниваете? Сделайте для каждого проперти один раз выражение и поместите в статичечкую переменную.

А ещё быстрее будет совсем без выражений: используйте делегат который будет возвращать нужное свойство:


Helper.Raise<A>(x => x.MyProperty);
Re[6]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 19.08.08 06:44
Оценка:
Здравствуйте, 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);
        }
    }


вот результат замера
sinclair:   0,9861  new:   0,6205  old:   0,0310
sinclair:   0,9440  new:   0,6583  old:   0,0253
sinclair:   0,9160  new:   0,7514  old:   0,0257
sinclair:   1,2343  new:   0,6406  old:   0,0260
sinclair:   0,9129  new:   0,6326  old:   0,0270
sinclair:   0,9305  new:   0,6435  old:   0,0252
sinclair:   1,1298  new:   0,6982  old:   0,0262
sinclair:   1,1961  new:   0,6512  old:   0,0256
sinclair:   1,0052  new:   0,6505  old:   0,0254
sinclair:   0,9874  new:   0,6314  old:   0,0249


Вот еще один маленький фрагмент, для проверки работает ли кэширование
static void Main()
{
        A3 a3 = new A3();
        a3.PropertyChanged += delegate {  };
        for (int i = 0; i < 5; i++)
        {
                a3.MyProperty = i.ToString();
        }
}
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;

                        Console.WriteLine("Property {0} => dictionary", args.PropertyName);
                }
                else
                {
                        Console.WriteLine("Property {0} <= dictionary", args.PropertyName);
                }
                handler(this, args);
        }
}

вывод на экран:
Property MyProperty => dictionary
Property MyProperty => dictionary
Property MyProperty => dictionary
Property MyProperty => dictionary
Property MyProperty => dictionary
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re[7]: Удобное получение имени свойства
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.08.08 07:15
Оценка:
Здравствуйте, SiAVoL, Вы писали:

SAV>Вот еще один маленький фрагмент, для проверки работает ли кэширование

Ага, интересно. Я полагал, что компилятор построит дерево статически и будет его повторно использовать в вызове.
Похоже, он так не сделал. Тогда простой способ — в том, чтобы принудительно применять один и тот же объект, но это будет некрасиво.
Значит, надо думать еще.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Удобное получение имени свойства
От: desco США http://v2matveev.blogspot.com
Дата: 19.08.08 07:16
Оценка: 159 (9)
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, SiAVoL, Вы писали:


SAV>>Мне очень давно не нравилось использование имен в виде строк в биндинге или, например, при реализации интерфейса INotifyPropertyChanged. Очень ненадежно и код так и норовит сломаться при рефакторинге. Вчера мне пришло в голову решение на основе Expression. Наверняка кто-то уже находил такое решение или даже использует его, потому что решение лежит на поверхности. Но мне такой подход до сих пор не попадался, поэтому надеюсь что кому-то мой пост будет интересен.

SAV>>Итак приведу небольшой класс, глядя на который будет ясно что я имею в виду:
S>Правильной дорогой идете, товарищи. Аналогичные трюки применяют в ASP.NET MVC Framework.
S>Я бы это назвал "использование лямбд для записи ссылок на мемберы".
S>Общий принцип: делаем лямбду, которая никогда не вызывается. Вместо этого она используется для интроспекции и получения побочных эффектов.

S>А теперь попробуем выполнить некоторый оверклокинг:

S>
S>private static Dictionary<Expression, PropertyChangedEventArgs> _propertyArgs = new Dictionary<Expression, PropertyChangedEventArgs>();
S>private void InvokePropertyChanged<T>(Expression<Func<T>> property)
S>{
S>    PropertyChangedEventHandler handler = PropertyChanged;

S>    if (handler == null)
S>        return;

S>    PropertyChangedEventArgs args;

S>    if (!_propertyArgs.TryGetValue(property, out args))
S>    {
S>        MemberExpression expression = (MemberExpression) property.Body;
S>        args = new PropertyChangedEventArgs(expression.Member.Name);
S>        _propertyArgs[property] = args;
S>    }
S>    handler(this, args);
S>}
S>

S>Померишь скорость?

не спасет. Основное место, где проседает производительнось — собственно генерация Expression.
код
        public string MyProperty
        {
            get { return _myProperty; }
            set
            {
                _myProperty = value;
                InvokePropertyChanged(() => MyProperty);
            }
        }


преобразуется компилятором во что-то типа такого
        public string MyProperty
        {
            get { return _myProperty; }
            set
            {
                _myProperty = value;
                InvokePropertyChanged(
                    Expression.Lambda<Func<string>>(
                        Expression.MakeMemberAccess(Expression.Constant(this), typeof(A1).GetProperty("MyProperty"))
                        )
                    );
            }
        }


то есть основное время будет тратиться именно на вызов Expression.Lambda.

Если класс A1 из исходного примера слегка преобразовать так, чтобы Expression генерился единожды, то результаты меняются:
    class A1 : INotifyPropertyChanged
    {
        private string _myProperty;
        private static Expression<Func<A1, string>> myProperty = _ => _.MyProperty; 

        public string MyProperty
        {
            get { return _myProperty; }
            set
            {
                _myProperty = value;
                InvokePropertyChanged(myProperty);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void InvokePropertyChanged<T>(Expression<Func<A1, T>> property)
        {
            PropertyChangedEventHandler Handler = PropertyChanged;
            if (Handler != null)
            {
                MemberExpression expression = (MemberExpression)property.Body;
                Handler(this, new PropertyChangedEventArgs(expression.Member.Name));
            }
        }
    }

new: 00:00:00.0537428 old: 00:00:00.0516998
new: 00:00:00.0547094 old: 00:00:00.0498622
new: 00:00:00.0485765 old: 00:00:00.0501952
new: 00:00:00.0546304 old: 00:00:00.0503083
new: 00:00:00.0514889 old: 00:00:00.0506022
new: 00:00:00.0507240 old: 00:00:00.0521873
new: 00:00:00.0549547 old: 00:00:00.0521745
new: 00:00:00.0503452 old: 00:00:00.0526346
new: 00:00:00.0502642 old: 00:00:00.0506511
new: 00:00:00.0534263 old: 00:00:00.0532698

... << RSDN@Home 1.2.0 alpha 4 rev. 1090>>
Re[6]: Удобное получение имени свойства
От: adanov  
Дата: 19.08.08 07:20
Оценка:
Здравствуйте, Curufinwe, Вы писали:

C>Что же Вы два разных экземпляра сравниваете? Сделайте для каждого проперти один раз выражение и поместите в статичечкую переменную.


Дык как же в статическую переменную можно поместить ссылку на выражение с нестатическим свойством?
плавали, влипли
Re[7]: Удобное получение имени свойства
От: Curufinwe Украина  
Дата: 19.08.08 07:30
Оценка: +1
Здравствуйте, adanov, Вы писали:

A>Здравствуйте, Curufinwe, Вы писали:


C>>Что же Вы два разных экземпляра сравниваете? Сделайте для каждого проперти один раз выражение и поместите в статичечкую переменную.


A>Дык как же в статическую переменную можно поместить ссылку на выражение с нестатическим свойством?

A>плавали, влипли

Смотри ответ desco.
Re[2]: Удобное получение имени свойства
От: Lloyd Россия  
Дата: 19.08.08 14:05
Оценка:
Здравствуйте, akarinsky, Вы писали:

A>Давным давно используется, проблем никаких, рекомендую. Нет, вру: иногда забывают объявить свойство виртуальным, но это лечится.


При этом появляется другие проблемы — навскидку, проконтролировать, что все объекты создаются через фабрику, нужно помнить, что имя класса будет оличаться, делать все свойства виртуальными.
... << RSDN@Home 1.2.0 alpha rev. 786>>
Re[2]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 19.08.08 14:30
Оценка:
Здравствуйте, 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.
Только вот не везде такие решения нужны и оправданы. А красиво жить хочется всегда.
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re[2]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 19.08.08 14:31
Оценка:
Здравствуйте, 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.
Только вот не везде такие решения нужны и оправданы. А красиво жить хочется всегда.
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.