Какая магия компилятора нам нужна больше?

Я разрабатываю модели просмотра участков, которые:

1) Все должны реализовать INotifyPropertyChanged для привязки к пользовательскому интерфейсу.

2) Установщики свойств должны поднимать PropertyChanged при изменении.

3) Событие PropertyChanged должно предоставить правильное имя свойства.

Если вы (как и я) привязались к написанию чего-то вроде этого:


public string Name 
{
  get 
  { 
    return _name; 
  }
  set 
  { 
    if (_name != value) 
    {
      _name = value;
      RaisePropertyChanged("Name");
    }
  }
} 

Затем реорганизуйте этот метод следующим образом и иногда забудьте обновить литерал имени свойства:


string _fundName;
public string FundName 
{
  get 
  { 
    return _fundName; 
  }
  set 
  { 
    if (_fundName != value) 
    {
      _fundName = value;
      RaisePropertyChanged("Name");
    }
  }
} 

А затем потратьте день на отладку, почему ваш пользовательский интерфейс не обновляется, а привязка данных не работает должным образом.

Тогда все, что нам нужно, это какое-то волшебство.

Что, если мне просто нужно написать это:


[Magic] // implicit transformation
public string FundName { get; set; }

или если у меня много свойств:


[Magic]
public class MyViewModel
{
  public string FundName { get; set; }
  public string FundType { get; set; }

  [NoMagic] // suppress transformation
  public int InternalId { get; set; }
}

Поэтому я только что разработал задачу MSBuild, чтобы проделывать эту магию после сборки (http://kindofmagic.codeplex.com ).

Вопрос в том, какую волшебную постобработку вы хотели бы больше?

Имеет ли смысл автоматическая реализация INotifyPropertyChanging?


person Lex Lavnikov    schedule 01.12.2010    source источник
comment
Как насчет того, чтобы создать то же самое для INotifyPropertyChanging?   -  person Matěj Zábský    schedule 01.12.2010
comment
@CommanderZ вы внедряете и используете INotifyPropertyChanging? Если да то зачем? возможно, вы хотите прокомментировать stackoverflow.com/questions/3835788/   -  person Simon    schedule 02.12.2010


Ответы (5)


Если мы собираемся иметь причудливую генерацию кода, я думаю, что предпочел бы более простой способ генерации DependancyProperties. Фрагмент, который я использую, безусловно, полезен, но мне не нравится, как беспорядочно выглядит код, когда у вас есть обратные вызовы on-changed и coerce, а также параметры метаданных. Может быть, я попытаюсь смоделировать образец после работы.

Редактировать. Вот одна концепция. Было бы намного умнее, если бы вы передавали анонимные методы атрибутам, но это все же шаг вперед.

До:

[DpDefault("The Void")]
[DpCoerce(new CoerceValueCallback(MainWindow.CoerceAddress))]
[DpChanged(new PropertyChangedCallback(MainWindow.ChangeAddress1))]
[DpChanged(new PropertyChangedCallback(MainWindow.ChangeAddress2))]
[DpOptions(FrameworkPropertyMetadataOptions.Inherits)]
public string Address {
    get { return Dp.Get<string>(); }
    set {
        if (Dp.Get<string>() != value) {
            Dp.Set(value);
            PostOffice.SendMailToTheBoss("I moved!");
        }
    }
}

После:

public string Address {
    get { return (string)GetValue(AddressProperty); }
    set {
        if ((string)GetValue(AddressProperty) != value) {
            SetValue(AddressProperty, value);
            PostOffice.SendMailToTheBoss("I moved!");
        }
    }
}

public static readonly DependencyProperty AddressProperty =
    DependencyProperty.Register("Address", typeof(string), typeof(MainWindow),
        new FrameworkPropertyMetadata((string)"The Void",
            FrameworkPropertyMetadataOptions.Inherits,
            new PropertyChangedCallback(MainWindow.ChangeAddress1)
                + new PropertyChangedCallback(MainWindow.ChangeAddress2),
            new CoerceValueCallback(MainWindow.CoerceAddress)));

Как правило, используется только атрибут 'DpDefault', но даже если он не делает код короче, он определенно делает его понятнее. Вот более типичный пример:

До:

[DpDefault("The Void")]
public string Address { get; set; }

После:

public string Address {
    get { return (string)GetValue(AddressProperty); }
    set { SetValue(AddressProperty, value); }
}

public static readonly DependencyProperty AddressProperty =
    DependencyProperty.Register("Address", typeof(string), typeof(MainWindow),
        new UIPropertyMetadata((string)"The Void"));
person YotaXP    schedule 01.12.2010
comment
Хорошая идея. Мы можем сгенерировать свойство зависимости из обычного, извлекая сеттер в обработчике OnDependencyPropertyChanged. И создайте статическое поле DependencyProperty только для чтения с правильно полученным именем, если оно не существует. - person Lex Lavnikov; 01.12.2010
comment
Отредактировал ответ с концепцией макета того, как может выглядеть преобразование кода. - person YotaXP; 02.12.2010

Попробуй это

http://code.google.com/p/notifypropertyweaver/

  • Атрибуты не требуются
  • Ссылки не требуются
  • Базовый класс не требуется

Вот моя статья в блоге об этом

http://codesimonsays.blogspot.com/2010/11/attempting-to-solve-inotifypropertychan.html

Он поддерживает атрибуты, которые вы запрашиваете

  • NotifyPropertyAttribute (уведомление о свойстве)
  • NotifyForAllAttribute (уведомление обо всех свойствах типа)
  • NotifyIgnoreAttribute (не уведомлять о свойстве или типе)
  • ТакжеNotifyFor (позволяет внедрить код уведомления, указывающий на другое свойство)

Хотя это опционально и предназначено для тонкой настройки. Большая часть инъекций выполняется по соглашению путем анализа существующего IL.

person Simon    schedule 01.12.2010
comment
Возможно из-за чужого хостинга проекта ;) - person Lex Lavnikov; 02.12.2010
comment
Кстати, понравилась идея с удалением ссылки. Я пришел только для удаления атрибута ;) - person Lex Lavnikov; 02.12.2010
comment
@Lex рад, что тебе понравилось. Мой подход похож на то, что вы планировали? Какие еще функции вы можете придумать? - person Simon; 02.12.2010
comment
@Lex Я также поддерживаю, чтобы у тебя были свои собственные атрибуты. См. раздел Отсутствие ссылок в файле проекта code.google.com/p/notifypropertyweaver/wiki/ - person Simon; 02.12.2010

«Магия» почти всегда является ужасным названием для метода, свойства или переменной на любом языке. Вы должны переименовать атрибут во что-то более описательное. Представьте, что вы просто случайный интернет-прохожий и натыкаетесь на кусок кода с атрибутом «Магия», что он говорит вам о коде? Точно ничего :)

Я все равно попробую ваш код, он может сэкономить время. Это определенно должно быть частью .NET.

person Matěj Zábský    schedule 01.12.2010
comment
Атрибут под названием «Магия» определенно привлечет мое внимание. Но, я согласен с вами. Ищете лучшее имя, есть идеи? - person Lex Lavnikov; 01.12.2010
comment
Это не совсем ответ. - person Simon; 02.12.2010
comment
Что не так с [NotifyPropertyChanged]int SomeProperty {get; задавать;} - person Matěj Zábský; 02.12.2010

Что-то, что может сделать вашу жизнь немного проще, это... (Я взял это от Caliburn Micro).

 public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property) {
            NotifyOfPropertyChange(property.GetMemberInfo().Name);
        }

Это позволяет вам делать следующее.

NotifyOfProperyChange(() => this.PropertyName);

Это позволит выделить любые проблемы с кодом во время разработки, а не во время выполнения.

Caliburn Micro — отличная небольшая структура, на которую вам стоит взглянуть, поскольку она удаляет так много проводов, связанных с MVVM и Silverlight/WPF!

person BenjaminPaul    schedule 01.12.2010
comment
Я тоже пользуюсь C.M. Но, честно говоря, это много потраченных впустую циклов процессора только для того, чтобы получить ПРАВИЛЬНОЕ имя свойства. Основная причина создания проекта KindOfMagic — сделать его максимально производительным и с нулевым набором текста для пользователей. - person Lex Lavnikov; 01.12.2010

Это уже можно сделать с помощью инструмента АОП (аспектно-ориентированного программирования), такого как PostSharp: http://www.richard-banks.org/2009/02/аспектно-ориентированноепрограммирование.html (с использованием v1.x) http://www.sharpcrafters.com/solutions/ui#data-binding (используя v2.0)

Я использовал PostSharp для реализации INPC в нескольких проектах, и это сработало очень хорошо, код стал намного чище и удобнее в сопровождении (добавляет несколько секунд к времени компиляции).

person Catalin DICU    schedule 01.12.2010
comment
Я попробовал PostSharp для INPC месяц назад, но сгенерированный IL-код для сеттеров был таким раздутым и уродливым (я использовал Reflector). Я считаю, что общие инструменты АОП создают слишком большие накладные расходы для этой крошечной конкретной проблемы. - person Lex Lavnikov; 01.12.2010
comment
Вы подтвердили это мнение каким-либо тестированием производительности? По моему опыту, накладные расходы, вносимые АОП, в этом случае незаметны. - person Robert Rossney; 01.12.2010
comment
re postsharp bloat вводит около 150 строк кода в каждое свойство. - person Simon; 02.12.2010
comment
@Robert, мне просто нужно правильно проверить старое-новое значение и вызвать событие, если оно отличается. Мне не нужны ни Starship Enterprise, ни Battlestar Galactica. - person Lex Lavnikov; 02.12.2010
comment
@Simon, он может вводить 10000 строк кода в каждое свойство, и это не беспокоит меня, пока пользователи не заметят разницы в производительности. Однако я не трачу много времени (да и вообще ничего) на ковыряние в своих сеттерах с помощью Reflector, так что, возможно, я делаю что-то не так. Или вправо. - person Robert Rossney; 02.12.2010
comment
@Rob Меня не беспокоят последствия для производительности. Меня беспокоит размер моей сборки в несколько раз больше из-за 150 строк кода для каждого набора. - person Simon; 03.12.2010
comment
@Rob Я думаю, все сводится к компромиссам. Postsharp — отличный инструмент, но ценностное предложение его использования для уведомлений о свойствах размыто из-за проблем, упомянутых выше. - person Simon; 03.12.2010