Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 18.08.08 06:20
Оценка: 57 (8)
Мне очень давно не нравилось использование имен в виде строк в биндинге или, например, при реализации интерфейса 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));
            }
        }
    }

При таком подходе можно быть спокойным во время рефакторинга.
Конечно же меня заинтересовало насколько просядет производительность при таком коде
new:   0,6813  old:   0,0260
new:   0,5975  old:   0,0253
new:   0,5897  old:   0,0264
new:   0,5876  old:   0,0262
new:   0,5902  old:   0,0264
new:   0,5923  old:   0,0261
new:   0,6267  old:   0,0254
new:   0,5844  old:   0,0252
new:   0,5796  old:   0,0265
new:   0,5840  old:   0,0260

Видно, что "новый" подход примерно в 20 раз медленнее классического. Но в принципе для любителей "не экономить на спичках", чем не вариант?
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
код теста
От: SiAVoL Россия  
Дата: 18.08.08 06:21
Оценка:
using System;
using System.ComponentModel;
using System.Linq.Expressions;

namespace testPropertyName
{
    class Program
    {
        static void Main()
        {
            for(int i=0; i<10; i++)
            {
                TestIteration();
            }
        }

        static void TestIteration()
        {
            const int iterationsCount = 100000;

            A1 a1 = new A1();
            a1.PropertyChanged += delegate {  };

            PerfCounter counter = new PerfCounter();
            counter.Start();
            for (int i = 0; i < iterationsCount; i++)
            {
                a1.MyProperty = i.ToString();
            }
            float time1 = counter.Finish();

            A2 a2 = new A2();
            a2.PropertyChanged += delegate { };
            counter.Start();
            for (int i = 0; i < iterationsCount; i++)
            {
                a2.MyProperty = i.ToString();
            }
            float time2 = counter.Finish();

            Console.WriteLine("new: {0:### ### ##0.0000}  old: {1:### ### ##0.0000}", time1, time2);
        }
    }

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

    class A2 : INotifyPropertyChanged
    {
        private string _myProperty;

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

        public event PropertyChangedEventHandler PropertyChanged;

        private void InvokePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler Handler = PropertyChanged;
            if (Handler != null)
            {
                Handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re: Удобное получение имени свойства
От: _FRED_ Черногория
Дата: 18.08.08 06:30
Оценка: -3
Здравствуйте, SiAVoL, Вы писали:

SAV>Мне очень давно не нравилось использование имен в виде строк в биндинге или, например, при реализации интерфейса INotifyPropertyChanged. Очень ненадежно и код так и норовит сломаться при рефакторинге.


Вот это и надо лечить, объявляя константы с именами свойств, а не изобретать квадратные колёса.

SAV>Вчера мне пришло в голову решение на основе Expression.


Потеряешь на встраивании. Проверять надо на релизе при запуске не из под отладчика.
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 18.08.08 06:56
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Вот это и надо лечить, объявляя константы с именами свойств, а не изобретать квадратные колёса.

А как константы помогут при рефакторинге? И или предлагается сделать public константу с именем свойства, которую везде и использовать, а при рефакторинге не забывать изменить эту константу руками? По мне эти колеса нисколько не круглее.

_FR>Проверять надо на релизе при запуске не из под отладчика.

я и так проверял на релизе не из-под отладчика.
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re: Удобное получение имени свойства
От: arkhivania  
Дата: 18.08.08 07:16
Оценка:
Здравствуйте, SiAVoL, Вы писали:

Если вы используете данное в WPF, то в sp1 для .Net 3.5 заработало долгожданное event EventHandler PropertyNameChanged.
Re: Удобное получение имени свойства
От: RobertT Россия http://bobbbloggg.blogspot.com/ http://robbbloggg.blogspot.com/
Дата: 18.08.08 07:39
Оценка: 24 (3)
Да, вы правы, об этом уже писали:
http://www.clariusconsulting.net/blogs/kzu/archive/2006/07/06/TypedReflection.aspx
http://www.clariusconsulting.net/blogs/kzu/archive/2007/12/30/49063.aspx

но я думаю от этого ваше решение не становится менее ценным, ведь оно ваше, а я прежде чем подумать, к сожалению, нашел эти статьи =)
Re[2]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 18.08.08 07:50
Оценка:
Здравствуйте, 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:   1,3459  adanov:   0,0544  old:   0,0398
SiAVoL:   1,3257  adanov:   0,0522  old:   0,0398
SiAVoL:   1,3192  adanov:   0,0521  old:   0,0394
SiAVoL:   1,3193  adanov:   0,0524  old:   0,0397
SiAVoL:   1,3163  adanov:   0,0518  old:   0,0395
SiAVoL:   1,3201  adanov:   0,0522  old:   0,0400
SiAVoL:   1,3174  adanov:   0,0530  old:   0,0397
SiAVoL:   1,3266  adanov:   0,0521  old:   0,0392
SiAVoL:   1,3245  adanov:   0,0522  old:   0,0395
SiAVoL:   1,3266  adanov:   0,0517  old:   0,0398

Получается чуть медленнее оригинального (но с пустыми обработчиками !!!!).
Но если свойство имеет тип System.Int32, результаты значительно отличаются:
SiAVoL:   1,2349  adanov:   0,0147  old:   0,0036
SiAVoL:   1,1984  adanov:   0,0122  old:   0,0031
SiAVoL:   1,1949  adanov:   0,0122  old:   0,0033
SiAVoL:   1,2124  adanov:   0,0119  old:   0,0031
SiAVoL:   1,2007  adanov:   0,0121  old:   0,0034
SiAVoL:   1,1924  adanov:   0,0119  old:   0,0031
SiAVoL:   1,1948  adanov:   0,0120  old:   0,0035
SiAVoL:   1,1947  adanov:   0,0122  old:   0,0031
SiAVoL:   1,1846  adanov:   0,0119  old:   0,0033
SiAVoL:   1,1842  adanov:   0,0120  old:   0,0031

т.е. улучшение в 100 раз при безопасном рефакторинге
Re[2]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 18.08.08 12:32
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Предлагаю следующее решение, почти без затрат времени, и писанины меньше:

либо я чего-то не понял, либо предполагается, что у каждого класса будет не более одного свойства с поддержкой уведомления об изменениях.
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re[3]: Удобное получение имени свойства
От: arkhivania  
Дата: 18.08.08 12:37
Оценка:
Здравствуйте, SiAVoL, Вы писали:

SAV>Здравствуйте, <Аноним>, Вы писали:


А>>Предлагаю следующее решение, почти без затрат времени, и писанины меньше:

SAV>либо я чего-то не понял
Видимо вы не поняли, имя свойства берется из стека.
А про то что INotifyPropertyChanged типа хорошее решение (меньше кода и не нужно плодить ивентов), думаю что с этим согласится меньше 1% проектировщиков.
Re[4]: Удобное получение имени свойства
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 18.08.08 12:45
Оценка: +2
Здравствуйте, arkhivania, Вы писали:

A>Видимо вы не поняли, имя свойства берется из стека.


А ты в курсе про такую вещь как инлайнинг?
... << RSDN@Home 1.2.0 alpha 4 rev. 1106 on Windows Vista 6.0.6001.65536>>
AVK Blog
Re[5]: Удобное получение имени свойства
От: arkhivania  
Дата: 18.08.08 12:50
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>А ты в курсе про такую вещь как инлайнинг?


Я в курсе, решение со стеком это не мое решение (Аноним это не я).
Re[3]: Удобное получение имени свойства
От: adanov  
Дата: 18.08.08 12:54
Оценка:
Здравствуйте, SiAVoL, Вы писали:

SAV>... у каждого класса будет не более одного свойства с поддержкой уведомления об изменениях.

увы это так
будем искать другое решение
Re[2]: Удобное получение имени свойства
От: drol  
Дата: 18.08.08 13:03
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Предлагаю следующее решение, почти без затрат времени, и писанины меньше:

А>Вот мои результаты тестирования:
А>Получается чуть медленнее оригинального (но с пустыми обработчиками !!!!).
А>Но если свойство имеет тип System.Int32, результаты значительно отличаются:
А>т.е. улучшение в 100 раз при безопасном рефакторинге

А сколько получится, ежели проводить измерения при глубине стека вызовов в районе 30+ ?
Re[4]: Удобное получение имени свойства
От: adanov  
Дата: 18.08.08 13:04
Оценка:
Здравствуйте, adanov, Вы писали:

если без кэширования, очень медленно получается:
SiAVoL:   1,2355  adanov:   3,8960  old:   0,0038
SiAVoL:   1,2122  adanov:   3,9001  old:   0,0033
SiAVoL:   1,1945  adanov:   3,9075  old:   0,0030
SiAVoL:   1,1936  adanov:   3,9160  old:   0,0030
SiAVoL:   1,2041  adanov:   3,9539  old:   0,0030
SiAVoL:   1,2140  adanov:   3,8989  old:   0,0030
SiAVoL:   1,2208  adanov:   3,9206  old:   0,0035
SiAVoL:   1,2027  adanov:   3,9172  old:   0,0030
SiAVoL:   1,2046  adanov:   3,9226  old:   0,0032
SiAVoL:   1,2005  adanov:   3,8902  old:   0,0030
Re[2]: Удобное получение имени свойства
От: RobertT Россия http://bobbbloggg.blogspot.com/ http://robbbloggg.blogspot.com/
Дата: 18.08.08 13:14
Оценка: +3
мне кажется очень красивые колеса, элегантные. Возможно ездить на них и медленнее, но как средство от головной боли очень даже — какая разница какие колеса ;)
Re[4]: Удобное получение имени свойства
От: SiAVoL Россия  
Дата: 18.08.08 13:15
Оценка:
Здравствуйте, arkhivania, Вы писали:

A>Видимо вы не поняли, имя свойства берется из стека.

я все понял. Только суть не в этом, там словарь с ключем по типу. Т.е. вызвали метод из одного свойства, в словарь записался тип и имя свойства. Вызвали из другого свойства, полетело событие с именем первого свойства. Мне кажется, что это неправильное поведение. Впрочем автор со мной уже согласился уже согласился
Автор: adanov
Дата: 18.08.08


A>А про то что INotifyPropertyChanged типа хорошее решение (меньше кода и не нужно плодить ивентов), думаю что с этим согласится меньше 1% проектировщиков.

А чем решение плохо? У него есть своя обширная ниша — бизнес-объекты. Для ивента на каждое свойство нужно:
— во-первых писать эти ивенты. Да, можно сделать снипеты, шаблоны и пр.
— во-вторых на каждое свойство надо держать поле-делегат. А они память кушают. А подписываться на все события нужно далеко не всегда. Да, можно переопределить add/remove для события.
Все решаемо, но для чего плодить сложности если для классов бизнес-объектов INotifyPropertyChanged нужен в основном для связи с UI?
Для компонентов, не спорю, часто отдельные события предпочтительнее.
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re: Удобное получение имени свойства
От: Curufinwe Украина  
Дата: 18.08.08 13:16
Оценка: 5 (1)
Здравствуйте, SiAVoL, Вы писали:

SAV>Видно, что "новый" подход примерно в 20 раз медленнее классического. Но в принципе для любителей "не экономить на спичках", чем не вариант?


Сам такой тоже писал пол года назад

Учитывая, что подписчик на данное свойство обычно использует reflection чтобы вытащить новое значение свойства — разница в скорости совсем будет не заметна

Можно ещё лямбда-выражения сделать статическими членами класса, а в хелпере использовать хеш-таблицу для хранения уже вычисленных имён свойств.
Но я думаю, что в типичных сценариях эта дополнительная писанина/оптимизация себя не оправдает.
Re[5]: Удобное получение имени свойства
От: arkhivania  
Дата: 18.08.08 13:45
Оценка:
Здравствуйте, SiAVoL, Вы писали:

Вообще если нужно сверхскоростное решение, то придумать такое могу ), правда с небольшими ограничениями.
1) Свойство помечается неким своим аттрибутом.
2) Далее в сеттере используете например свой способ (или любой "медленный")
-- до этого места всё как обычно, работаете, отлаживаете, а вот в релизе

3) Пишете утилиту, которая при помощи Cecil заменяет медленный способ на стандартный у всех свойств помеченных вашим аттрибутом.

Будет работать шустро )
Re[6]: Удобное получение имени свойства
От: Curufinwe Украина  
Дата: 18.08.08 13:59
Оценка:
Здравствуйте, arkhivania, Вы писали:

A>3) Пишете утилиту, которая при помощи Cecil заменяет медленный способ на стандартный у всех свойств помеченных вашим аттрибутом.


Раз уж пошло такое дело, то используя PostSharp например, можно весь интерфейс INotifyPropertyChanged реализовать одним атрибутом на классе:
http://www.acceptedeclectic.com/2008/01/postsharp-part-2-databinding-support.html
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...
Пока на собственное сообщение не было ответов, его можно удалить.