Pull to refresh

Comments 43

Спасибо, очень интересное решение, как минимум в плане отсутствия написания одинакового кода. Правда к подобным ухищрение приводит печальное отсутствие макросов образца Nemerle.
Nemerle хорош, но хотелось бы таких же наворотов в c#
А чем не устраивает Nemerle?
Он хорош. Честное слово, очень хорош. Если бы вместо F# был бы он — то я бы с удовольствием писал на нем. А так — никто не даст в коммерческом проекте использовать малоизвестный не поддерживаемый майкрософтом язык.
Mono.Cecil это просто библиотека, которая так называется. Она к mono не привязана.
Не поверите, но я знал об этом.
Здорово, но атрибут лучше переименовать во что-нибудь более адекватное, например: NotifyOnChanged.
Не первый раз слышу такое предложение, для этого даже параметризировал названия атрибутов в .targets файле.
Попутный вопрос.
Я немного далек от INPC, но RaisePropertyChanged — это устоявшаяся форма вызова события? Или все-таки по гайдлайнам virtual protected метод для вызова события должен наименоваться OnPropertyChanged и уже иметь в качестве аргумента PropertyChangedEventArgs?
Raise обычно. и логично. On- как раз для virtual методов и это уже совсем другой подход, основанный не на делегатах, а на перегрузке. привык искать Raise-, но иногда попадаются On-

видимо, по инерции бывших VB-кодеров
В идеале — сделать поддержку сразу нескольких «стилей»: RaisePropertyChanged, OnPropertyChanged, NotifyPropertyChanged, InvokePropertyChanged.
Этот метод является небольшим исключением.

Во-первых, он не совсем обязательно должен быть protected.

Во-вторых, если параметром будет PropertyChangedEventArgs — это просто праздник какой-то в каждом сеттере его создавать :)
Это такой шаблон, по ссылке объясняется почти каждое ключевое слово в нём.
RaiseEventName — для открытых методов, вызывающих событие. OnEventName — для защищённых методов, возможно предназначенных для переопределения в наследниках.

Само название метода RaiseEventName говорит о том, что он предназначен для вызова извне.
Ну так здесь как раз метод объявлен как protected virtual, т.е. для наследников для возможного переопределения.
Вот такой нюанс. Да впрочем мелочи всё это =)

Есть еще вариант с работой через прокси объект.
Пробовал такой механизм на NHibernate, где и так прокси генерится (для lazy), поэтому лишнего расхода нет.
При этом достаточно свойства сущностей объявлять как virtual, а в генераторе прокси написать единообразную обработку вызова сеттеров.

Ну а про MVVM — все-таки там не только нотификация изменений. Да и этот уровень, он ведь ViewModel, модель представления, он не должен быть тяжеловесным, да и производительность не так критична, т.к. «тормозом» обычно является пользователь.
Давно уже ни для кого не секрет, что DependencyProperty тормознее ручной реализации INPC.

Ну и установка значения прям в field будет, наверное, быстрее, чем в property. Но нам это не так интересно. Скорее всего нужно думать в случае WPF и Silverlight об установке значений в свойства, а так же о времени Binding, то есть передачи значения самому контролу, а вот тут вот ситуация меняется в другую сторону. Не даром вы говорите о множестве полей у объекта, а так же о множествах ViewModel привязанных к DataGrid.

По WPF есть набор статей связанных с оптимизацией на MSDN, вот одна из них касательно вашей темы Optimizing Performance: Data Binding. И там как раз DP работает шустрее CLR объекта с INotifyPropertyChanged. Но ненамного, то есть это не есть место для оптимизации. В Silverlight, думаю, все должно быть примерно так же.

Собственно, часть статьи про оптимизацию считаю неоправданной: не там и не то оптимизировали. А по поводу AOP — это бесспорно хороший вариант. Есть еще более простой вариант — написать свой сниппет в VS. Типа такого C# Code Snippets for properties raising PropertyChanged event.
Сниппеты не рефакториабельны без Решарпера, плюс ко всему — очень много букаф ;)

Но согласен, до KindOfMagic моим ежедневным решением были именно сниппеты.

Насчет, производительности data-binding — msdn-статье доверяй, но проверяй. У меня dependencyObjects ну никак быстрее не получаются, чем обычные свойства.

И этому есть объктивные причины:
1) прямой доступ без Dictionary
2) родная проверка на изменение

В чем могут dependency properties выигрывать — так это в боксинге. Происходит один раз в сеттере и геттере, а так везде как Object передается и хранится.
На сколько я понимаю вы тестируете просто тупо установку значений в свойства, так? Тут очевидно, что DP должен проигрывать. А вот в статье говорится о времени байдинга, как раз то, что нам очень важно в SL/WPF.
То есть верным тестом было бы создание для каждого свойства байдинга к какому-нибудь объекту и уже потом установка свойств, и подсчет времени.
Байдинг от слова байда или байдарка? ;)

Да, в тест-проекте тестируется только запись свойства и перезапись с другим значением. Это нужно для того, чтобы выпятить на поверхность тот функционал, производительность которого я и хочу сравнивать.

Мой комментарий по поводу производительности дата-байндинга относился именно к впечатлению вцелом.

Давайте не будем забывать, что dependency properties это костыль, придуманный MS исключительно ради Attached properties, типо Grid.Column.

Остальное их испотзование притянуто за уши, т.к. Xaml замечательно работает и с простыми свойствами.
Байдинг от слова байда или байдарка? ;)

Байдинг — так оно произносится, одно время тоже делали мне замечания по поводу «биндинг».

Да, в тест-проекте тестируется только запись свойства и перезапись с другим значением. Это нужно для того, чтобы выпятить на поверхность тот функционал, производительность которого я и хочу сравнивать.

В итоге вы не сравнили ничего, точнее сравнили, но впустую. DependencyObject нужно использовать только в контексте с байдингом, отдельно смысла от него ну совсем нет. Тестировать тоже с ним.

Давайте не будем забывать, что dependency properties это костыль, придуманный MS исключительно ради Attached properties, типо Grid.Column.

Это ваша додумка. Я с вами тут не согласен. Дам встречный вопрос: «Сколько значений в одно время может иметь Dependency Property?». Вот-вот, обычный CLR property такое не позволит, а в случае с анимацией, стилями — это полезная штука.

Binding произносится как байндинг согласно всем моим англоязычным коллегам. Но может они ошибаются, плохо знают родной английский.

В итоге вы не сравнили ничего, точнее сравнили, но впустую. DependencyObject нужно использовать только в контексте с байдингом

Я сравнивал запись в свойство, а также возможность реагировать на изменение этого свойства изнутри класса. Понятное дело, что реагировать на изменение свойства хотелось бы в его родном виде, поэтому везде стоит typecast. Это мой обычный сценарий. Никаких натяжек.

Выигрывая в транспортировке значения как object в случае композитных data-binding, мы проигрываем в использовании этого свойства в коде, т.к. typecast и извлечение значение из Dictionary по ключу инстанса DependencyProperty — это все-таки накладные расходы.

Еще интересно покопаться в классах CLRPropertyListener и DependencyPropertyListener, станет ясно, что логика везде одинаковая…

Сколько значений в одно время может иметь Dependency Property?

Я не умоляю достоинств DependencyProperties, просто их место в контролах, которые уже написаны за нас. В статье же речь идет об удобном варианте создания ViewModel-ей. И как мне показалось, использовать DependencyObject в этом контексте не совсем разумно. Ну если только вам необходима анимация свойств у ваших ViewModel-ей :)

А насчет анимации — соглашусь, не подумал. Хотя уверен, что простые свойства тоже поддаются анимации. Другое дело, что это будет уже Frame-based, а не Time-based анимация, которой MS так гордится.
Все верно, в реализации своих ViewModel лучший вариант — это INotifyPropertyChanged. В контролах DependencyProperty.

Выигрывая в транспортировке значения как object в случае композитных data-binding, мы проигрываем в использовании этого свойства в коде, т.к. typecast и извлечение значение из Dictionary по ключу инстанса DependencyProperty — это все-таки накладные расходы.

Еще интересно покопаться в классах CLRPropertyListener и DependencyPropertyListener, станет ясно, что логика везде одинаковая…


Это накладные расходы, но вот то, что это медленнее работает — это только ваша додумка. Еще раз: ваши тесты этого не подтверждают, они только проверяют то, что если в своем приложении реализовать свойства у какого-нибудь класса как обычные CLR с интерфейсом или как DependencyProperty (которые еще раз говорю в контексте не связывания с другими dependency property не имеют смысла). Вы не реализовали инфраструктуру WPF/Silverlight у себя в тестах.

И честно говоря, я не очень понимаю, зачем в этом месте что-то оптимизировать. Особенно в бизнес сценариях. Ну будет у вас DataGrid и 1000 ViewModel, а в каждом по 20 свойств, все из которых отображаете. Но не всех же 1000 моделей будут отображаться, у DataGrid есть виртуализация. 10 ms против 50 ms? Это выигрываем?
1000 моделей нужно заполнить данными до того, как хоть одна из них будут отображаться. Фактор 13 это много.
Это вместо 400ms получаем 5 секунд. 5 секунд 100% нагрузки на ядро.

Неудивительно, что потом люди начинают жаловаться, что Silverlight тормозит (или садит батарейки в моем планшете) :)

Давайте просто не будем писать тормозной код. Преждевременная оптимизация — это грех, согласен. Но выбор изначально тормозной технологии — 100% гарантия тормозов в будущем и существенные издержки на их устранение.

Врага нужно знать в лицо :)

Я не за DependencyProperty, так как уже говорил, что считаю их полезными только для собственных контролов. Я за использование Lambda варианта. А если будет проблема с производительностью в установке значений, то всегда можно сделать доп. метод для инициализации сущностей и т.п. В общем проблемы легко решаются.
По поводу DataGrid — как у Вас получилось достичь таких малых времён (50ms)?
У меня на проекте DataGrid с 30 строками и по ~10 свойств в каждой VM, даже при сортировке кликом по колонке (вне зависимости от того, встроенный сортировщик, или указан собственный) время перекомпоновки приближается к секунде.
Ну я это я чисто гипотетически :) DataGrid из тулкита не очень хорош
А можно показать какой код сеттера то в итоге генерится?? И за счет чего он становится аж в 3 раза быстрее рукописного??
В IL я генерю более простую проверку. C# подходит к делу более основательно, что в итоге слегка медленнее.
Был найден баг, сравнение строчек выполнялось только как reference.

Баг пофикшен, теперь все как обычно :)
А отлаживать такой код в студии возможно?
Возможно. Но сгенерированного кода, понятное дело, не видно.
Полный код теста в студию, плз. У меня особой разницы между Manual и Lambda нет.
Отставить тревогу. Разница есть.
> Поэтому я, как неисправимый преждевременный оптимизатор, склоняюсь именно к INPC.
однако, если переписать так, то разница будет абсолютно приемлимые 10%:
 
       staticExpression<Func<LambdaNPC, string>> MyPropertyExpression = o => o.MyProperty;
        private string _MyProperty;
        public string MyProperty
        {
            get { return _MyProperty; }
            set
            {
                if (_MyProperty == value)
                {
                    return;
                }
                _MyProperty = value;
                RaisePropertyChanged(MyPropertyExpression);
            }
        }

        void RaisePropertyChanged<T>(Expression<Func<LambdaNPC, T>> raiser)
        {
            var e = PropertyChanged;
            if (e != null)
            {
                var propName = ((MemberExpression)raiser.Body).Member.Name;
                e(this, new PropertyChangedEventArgs(propName));
            }
        }
За что боролись, на то и напоролись.

Кто-то хотел минимум кода при максимуме производительности ;)
В данном случае производительность должна быть не «о ужас, ужас» — этого уже достаточно, т.к. reflection при байндинге более узкое место по производительности. Конечно, INPC — это абсолютно точно реализация аспекта императивным программированием. Но пока, увы ни C#, ни VS, как платформа разработки, недружественны к AOP.
.net 4.0? :(
насколько сильны завязки? хочу попробовать портировать под 2.0 и интересно, достаточно ли переработать linq, или завязано глубже?
Sign up to leave a comment.

Articles