OnPropertyChanged со строгими именами

    Меня дико бесит OnPropertyChanged. Он требует передачи ему строки с именем идентификатора… Мало того, что такой код не верифицируется компилятором, так еще и никакого Intellisence — тайпи весь идентификатор от начала до конца… А если опечатаешся — так проглотит и во время выполнения. Вобщем, нехорошая вещь. Но в MVVM без нее никуда, как и в WPF без MVVM.

    Последняя проблема худо-бедно решалась вот таким костылем, содранным отсюда, который вызывался из OnPropertyChanged:
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
      // Verify that the property name matches a real, 
      // public, instance property on this object.
      if (TypeDescriptor.GetProperties(this)[propertyName] == null)
      {
        string msg = "Invalid property name: " + propertyName;

        if (this.ThrowOnInvalidPropertyName)
          throw new Exception(msg);
        else
          Debug.Fail(msg);
      }
    }


    И вот сегодня я совершенно случайно нашел красивый способ избавится от него.

    Решение оказалось красивым и одновременно простым до банальности: использовать Expression, вот так:
    this.OnPropertyChanged(() => this.ShowOverrideButton);

    Для этого нужно только создать в базовом классе перегрузку:
    protected void OnPropertyChanged<T>(Expression<Func<T>> property)
    {
      PropertyChangedEventHandler handler = this.PropertyChanged;
      if (handler != null)
      {
        var expression = property.Body as MemberExpression;
        if (expression == null)
        {
          throw new NotSupportedException("Invalid expression passed. Only property member should be selected.");
        }

        handler(this, new PropertyChangedEventArgs(expression.Member.Name));
      }
    }


    * This source code was highlighted with Source Code Highlighter.

    Готово. Можете забыть о строках в OnPropertyChange. Остаются еще нехорошие строки в XAML в Binding — но у меня совершенно нет идей как от них избавится.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 23

      0
      Тема сто раз жевана-пережевана на .NET ресурсах…

      ЗЫ. А меня дико бесит this и скобки, который пихают всюду, куда не лень :)
        +3
        Относительно жеванности — возможно, но я раньше не встречал, да и на Хабре вроде не было.
        А this и скобки всюда где не лень — ну, на вкус и цвет, что называется… StyleCop говорит надо, мы говорим — есть!
        0
        Так же, кстати, работают строго типизированные helper methods в ASP.NET MVC (типа Html.TextBoxFor(model=>model.Name)). И еще можно заглянуть в ExpressionHelper.GetRouteValuesFromExpression из сборки ASP.NET MVC Futures. Название класса говорит само за себя)
          0
          Ох удивили… вопрос уже обсосан до немогу. (Во что превращается Хабр?)
            0
            Можна пожалуйста ссылки на обсасывание? Интересно все-таки до чего договорились.
            +2
            Если вам нужно это использовать в WPF, зачем вы изобретаете велосипед (?), наследуйтесь от DependencyObject и используйте родные для WPF DependenctProperty.
            Работает такая схема на порядок быстрее, так как именно под это и заточена.
            Вот тут MVVM – Lambda vs INotifyPropertyChanged vs DependencyObject можно посмотреть результаты тестов использования разных методов.
            Вы даже не представляете на сколько сильно вы тормозите выполнение своими Expression.
            Хотя это еще что, я видел как под это дело рефлексию прикручивали…
              +1
              Я видел еще более извращенный способ: вместо NotifyPropertyChanged(«name») использовался метод Notify(), который анализировал фрэймы call stack'a, для выяснения из какого свойства его вызвали и вызывал для него NotifyPropertyChanged.

              Все хорошо в меру. Если у класса есть много редко вызываемых свойств, я предпочитаю использовать лямбды. Если несколько интенсивно используемых — DependencyObject.
                0
                А как с многопоточным кодом?
                +2
                Посмотрел я эти тесты — слов нет… одни матюки.
                Чувак в цикле присваивает одно и то же значение — реализация INotifyPropertyChanged и Lambda как пацаны диспетчят события… В то же время DependencyObject делает это только тогда, когда значение меняется…

                Добавил в те циклы динамическое значение:
                dependencyObjectViewModel.DummyProperty = «DummyText» + i;

                Сразу все стало на свои места:
                INotifyPropertyChanged = 20 секунд
                Lambda = 34 секунды
                DependencyObject = 18 секунд.

                Так что разгон про DependencyObject — от лукавого
                  0
                  У него рефлексия только в дебаге работала, и хотя требовала дополнительных телодвижений, типа добавления verify… все же быстрее чем expression
                  +2
                  Всё хорошо, но у меня код который должен работать на .net 2.0 и без поддержки рефлексии.
                  Потому к каждому класу с пропертями есть зеркальный клас с строковыми константами названиями пропертей(атрибуты нельзя, потомуму что изза драконовской секьрити не загружаются).
                  Потому вызов выглядит что-то вроде:
                  InvokePropertyChanged(EntitityNames.PropertyName);
                  FindAllRefrences на таком подходе работать будет.
                    0
                    Для начала поправьте заголовок поста, имя события — OnPropertyChanged.
                    А во вторых, вы понимаете, что каждый вызов
                    
                    this.OnPropertyChanged(() => this.ShowOverrideButton);
                    приводит к созданию новой копии делегата, передаваемого в качестве параметра?
                      0
                      1. Спасибо, исправил.
                      2. Вы имеете ввиду возможные memory leaks из-за сильной связаности?
                        0
                        Нет, я хочу сказать, что для свойства, значение которого часто меняется, будет сгенерирована тонна мусора, по одному экземпляру Delegate на каждый вызов.
                          0
                          Сейчас, что бы было совсем понятно, код
                          
                          this.OnPropertyChanged(() => this.ShowOverrideButton);
                          полностью аналогичен коду
                          
                          this.OnPropertyChanged(new Expression<Func<_type>>(new Func<_type>(() => this.ShowOverrideButton)));
                          , где _type — тип свойства.
                            +1
                            А нет, пардон, всё немного не так, код транслируется в такую вот хитрую штуку:
                            this.OnPropertyChanged<bool>(Expression.Lambda<Func<bool>>(Expression.Property(Expression.Constant(this, typeof(_owner_type)), (MethodInfo) methodof(_ownerType.get_ShowOverrideButton)), new ParameterExpression[0]));

                            Т.е. всё еще куда более запущено! При каждом вызове создаётся синтаксическое дерево с использованием рефлексии!
                            [Conditional(«DEBUG»)], конечно, отчасти дело спасает.
                              0
                              Эх, что-то у меня сутра с мозгами плохо совсем, [Conditional(«DEBUG»)] спасает первую версию, а версию с выражением — нет. ИМХО подобные накладные расходы в релизной версии никак не оправданы.
                                0
                                Мдя… Страшноватенько. Так что, человеческого решения нет?.. Тут выше кто-то кричал о обсосанности темы. Щас спрошу у них.
                                  0
                                  Меня вопрос заинтересовал, я сам намучался с рефакторингом классов, реализующих INotifyPropertyChanged «штатным» способом. Пока приемлемого решения мне на ум не приходило.
                                    0
                                    Я тут подумал: на худой конец можно использовать делегаты, а в Prebuild-event'ы засунуть sed который будет перемалывать их в обычные текстовые строки.

                                    … или просто забить, учитывая правило о преждевременной оптимизации.
                        0
                        Да, и по поводу DependencyObject — класс не заменим в ситуациях, когда у объекта много свойств, и у большей части экземпляров объектов значения этих свойств устанавливаются по умолчанию. Это даёт солидную экономию памяти. В купе с механизмом наследования значений и присоединенными свойствами, разработчик получает мощные возможности контроля над значениями свойств.
                        Однако если у класса мало свойств и их значения почти всегда разные, что очень характерно для классов, реализующих логику приложения или модель данных, то DependencyObject — не лучшее решение. Свойства зависимости хранятся вне экземпляров объектов в глобальной хеш-таблице, и само значение свойства занимает гораздо больше памяти, чем необходимо под экземпляр типа свойства. Поэтому использование INotifyPropertyChanged в модели данных — более чем оправдано.
                        Кроме того, нельзя получить прямой доступ к значению свойства зависимости из потока, которому не принадлежит DispatcherObject, даже на чтение.
                          +2
                          Я решил для себя эту проблему просто переписав propf сниппет. Добавил в сеттере строчку, бросающую PropertyChanged($PropertyName$)
                          Это решает почти все проблемы, ведь согласитесь, что 90-95% PropertyChanged бросается в сеттерах свойств.
                          Ну а если надо бросить его откуда-то извне — да, приходится писать ручками. Имхо проще, чем добавлять в каждый проект довольно много кода, плюс строками читаемость для других разработчиков проще.
                            0
                            На самом деле очень неважный вариант.

                            что мне помешает написать OnPropertyChanged(() => PropertyFromAnotherObject);

                            Вы перенесли потенциальную ошибку из рантайма в компилтайм, но безопасности не обеспечили…

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

                            Самое читаемое