Удобное получение имени свойства
От: 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
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.