Реализация INotifyPropertyChanged - существует ли лучший способ?


648

Microsoft должна была реализовать что-то быстрое INotifyPropertyChanged, например, в автоматических свойствах, просто укажите, {get; set; notify;} я думаю, что это имеет смысл делать. Или есть какие-то осложнения для этого?

Можем ли мы сами реализовать что-то вроде «уведомить» в наших свойствах. Есть ли изящное решение для реализации INotifyPropertyChangedв вашем классе или единственный способ сделать это - вызвать PropertyChangedсобытие в каждом свойстве.

Если нет, то можем ли мы написать что-нибудь, чтобы автоматически сгенерировать кусок кода, чтобы вызвать PropertyChanged событие?


7

7
Выше ссылка мертва. github.com/SimonCropp/NotifyPropertyWeaver
prime23

2
Вместо этого вы можете использовать DependencyObject и DependencyProperties. ХА! Я сделал смешно.
Фил


5
В то время внесение изменений в C # было невозможно, поскольку у нас был огромный бэк-журнал взаимозависимостей. Итак, когда родился MVVM, я думаю, мы просто не приложили особых усилий для решения этой проблемы, и я знаю, что команда Patterns & Practices несколько раз шла по пути (следовательно, вы также получили MEF как часть этого исследовательская тема). Сегодня я думаю, что [CallerMemberName] является ответом на вышесказанное.
Скотт Барнс

Ответы:


634

Без использования что-то вроде postsharp, минимальная версия, которую я использую, использует что-то вроде:

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

Каждое свойство тогда просто что-то вроде:

    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }

который не огромен; его также можно использовать как базовый класс, если хотите. boolВозвращение из SetFieldговорит вам , если это не было не-оп, в случае , если вы хотите применить другую логику.


или даже проще с C # 5:

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

который можно назвать так:

set { SetField(ref name, value); }

с помощью которого компилятор добавит "Name"автоматически.


C # 6.0 облегчает реализацию:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

... а теперь с C # 7:

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}

4
Хороший трюк Марк! Я предложил улучшить использование лямбда-выражения вместо имени свойства, см. Мой ответ
Томас Левеск

7
@Thomas - лямбда все хорошо, но она добавляет много накладных расходов на то, что на самом деле очень просто. Полезный трюк, но я не уверен, что он всегда практичен.
Марк Гравелл

14
@Marc - Да, возможно, это может ухудшить производительность ... Однако мне очень нравится тот факт, что он проверяется во время компиляции и корректно рефакторизован командой "Rename"
Томас Левеск

4
@Gusdor К счастью, с C # 5 нет необходимости идти на компромисс - вы можете получить лучшее от обоих через (как отмечает Pedro77)[CallerMemberName]
Марк Грэвелл

4
@ Gusdor язык и рамки являются отдельными; Вы можете использовать компилятор C # 5, целевую .NET 4 и просто добавить отсутствующий атрибут самостоятельно - он будет работать нормально. Он просто должен иметь правильное имя и быть в правильном пространстве имен. Это не должно быть в конкретной сборке.
Марк Гравелл

196

Начиная с .Net 4.5, наконец-то есть простой способ сделать это.

.Net 4.5 представляет новые атрибуты информации о вызывающем абоненте.

private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
     // make sure only to call this if the value actually changes

     var handler = PropertyChanged;
     if (handler != null) {
        handler(this, new PropertyChangedEventArgs(caller));
     }
}

Вероятно, это хорошая идея, чтобы добавить в функцию компаратор.

EqualityComparer<T>.Default.Equals

Больше примеров здесь и здесь

Также см. Информацию о вызывающем абоненте (C # и Visual Basic)


12
Brilliant! Но почему это общее?
Абатищев

@abatishchev Полагаю, этого не должно быть, я просто играл с идеей, чтобы функция также задавала свойство. Я посмотрю, смогу ли я обновить свой ответ и предоставить полное решение. Дополнительные примеры хорошо справляются с этой задачей.
Даниэль Литтл

3
Он был представлен C # 5.0. Это не имеет ничего общего с .net 4.5, но это отличное решение!
Дж. Леннон

5
@J. Lennon .net 4.5 все еще имеет к этому какое-то отношение, после того как атрибут откуда-то появился msdn.microsoft.com/en-au/library/…
Daniel Little

@Lavinski измените ваше приложение, например, на .NET 3.5 и посмотрите, что будет работать (в vs2012)
Дж. Леннон

162

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

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

Просто добавьте следующие методы к коду Марка, он сделает свое дело:

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    if (selectorExpression == null)
        throw new ArgumentNullException("selectorExpression");
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null)
        throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}

protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

Кстати, это было вдохновлено этим сообщением в блоге обновленный URL


6
Есть по крайней мере одна структура, использующая этот метод, ReactiveUI .
AlSki

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

1
@BrunoBrant Вы уверены, что есть снижение производительности? Согласно сообщению в блоге отражение происходит во время компиляции, а не во время выполнения (то есть статическое отражение).
Натаниэль Элкинс

6
Я считаю, что весь ваш OnPropertyChanged <T> устарел с оператором nameof C # 6, что делает этого монстра немного более изящным.
Traubenfuchs

5
@Traubenfuchs, на самом деле, атрибут CallerMemberName в C # 5 делает его еще проще, так как вам вообще ничего не нужно передавать ...
Томас Левеск

120

Есть также Fody, который имеет надстройку PropertyChanged , которая позволяет вам написать это:

[ImplementPropertyChanged]
public class Person 
{        
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
}

... и во время компиляции вставляет свойство измененных уведомлений.


7
Я думаю, что это именно то, что ищет OP, когда они спросили: «Можем ли мы сами реализовать что-то вроде« notify »в наших свойствах. Есть ли изящное решение для реализации INotifyPropertyChanged в вашем классе»
Ashoat

3
Это действительно единственное изящное решение, и оно работает без нареканий, как сказал @CADbloke. И я тоже скептически относился к ткачу, но я проверил / перепроверил код IL, и он идеален, прост, делает все, что нужно, и ничего больше. Он также перехватывает и вызывает любое имя метода, которое вы определили для него в базовом классе, независимо от того, имеют ли значение NotifyOnProp ..., OnNotify ..., поэтому он хорошо работает с любым базовым классом, который у вас может быть, и который реализует INotify .. .
NSGaga- в основном неактивными

1
Вы можете легко перепроверить, что делает ткач, взглянуть на окно вывода сборки, в нем перечислены все вещи PropertyChanged, которые он соткал. Использование расширения VScolorOutput с шаблоном регулярных выражений "Fody/.*?:",LogCustom2,Trueвыделяет его цветом «Custom 2». Я сделал его ярко-розовым, так что его легко найти. Просто Fody все, это самый лучший способ сделать что-нибудь, что имеет много повторяющихся печатать.
CAD парень

@mahmoudnezarsarhan нет, это не так, я помню, что в настройке было небольшое изменение, но Fody PropertyChanged все еще жив и активен.
Ларри

65

Я думаю, что люди должны уделять немного больше внимания производительности; это действительно влияет на пользовательский интерфейс, когда нужно связать много объектов (например, сетку с 10 000+ строками) или если значение объекта часто меняется (приложение для мониторинга в реальном времени).

Я взял различные реализации, найденные здесь и в других местах, и сделал сравнение; проверить это сравнение производительности реализации INotifyPropertyChanged .


Вот взгляд на результат Реализация против времени выполнения


14
-1: производительность не снижается: CallerMemberName изменяются на литеральные значения во время компиляции. Просто попробуйте декомпилировать ваше приложение.
JYL

вот соответствующий вопрос и ответ: stackoverflow.com/questions/22580623/…
uli78

1
@JYL, вы правы, что CallerMemberName не добавляет больших накладных расходов. Должно быть, я реализовал что-то не так в прошлый раз, когда попробовал. Я обновлю блог и ответ, чтобы отразить тест для реализации CallerMemberName и Fody позже.
Пейен

1
Если у вас в сетке 10 000+ в пользовательском интерфейсе, то вам, вероятно, следует сочетать подходы для управления производительностью, например, подкачку страниц, когда вы показываете только 10, 50, 100, 250 обращений на страницу ...
Остин Раймер,

Остин Раймер, если у вас большие данные + 50, используйте виртуализацию данных, нет необходимости загружать все данные, он будет загружать только те данные, которые видны в текущей отображаемой области прокрутки!
Билал

38

Я представляю класс Bindable в своем блоге по адресу http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/. Bindable использует словарь в качестве пакета свойств. Достаточно просто добавить необходимые перегрузки для подкласса, чтобы управлять его собственным полем поддержки, используя параметры ref.

  • Нет волшебной струны
  • Нет отражения
  • Может быть улучшено для подавления поиска в словаре по умолчанию

Код:

public class Bindable : INotifyPropertyChanged {
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    /// <summary>
    /// Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    protected T Get<T>([CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T)value;
        return default(T);
    }

    /// <summary>
    /// Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <remarks>Use this overload when implicitly naming the property</remarks>
    protected void Set<T>(T value, [CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(name)))
            return;
        _properties[name] = value;
        OnPropertyChanged(name);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Это можно использовать так:

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}

2
Это хорошее решение, но единственным недостатком является то, что есть небольшой удар по производительности, связанный с боксом / распаковкой.
MCattle

1
Я бы посоветовал использовать, protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)а также проверить if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))в Set (чтобы поднять и сохранить, когда первый раз установил значение по умолчанию)
Miquel

1
@Miquel Добавление поддержки пользовательских значений по умолчанию может быть полезно, однако вы должны быть осторожны, чтобы вызывать измененное событие только тогда, когда значение действительно изменилось. Установка для свойства того же значения, которое оно имело, не должно вызывать события. Я должен признать, что в большинстве случаев это безвредно, однако я несколько раз сталкивался с тем, что свойства устанавливали тысячи раз на одно и то же значение с событиями, разрушающими отзывчивость пользовательского интерфейса.
TiMoch

1
@stakx У меня есть несколько приложений, основанных на этом, чтобы поддерживать шаблон напоминания для отмены / повторения или чтобы включить шаблон единицы работы в приложениях, где nhibernate не может использоваться
TiMoch

1
Мне действительно нравится это конкретное решение: короткие нотации, отсутствие динамического прокси, отсутствие IL-вмешательства и т. Д. Хотя его можно сократить , устраняя необходимость каждый раз указывать T для Get, делая Get return динамическим. Я знаю, что это влияет на производительность во время выполнения, но теперь код для геттеров и сеттеров может, наконец, всегда быть одинаковым и в одну строку , хвала Господу! PS вы должны проявлять дополнительную осторожность внутри вашего метода Get (один раз, когда вы пишете базовый класс), когда возвращаете значения по умолчанию для типов значений как динамические. Обязательно всегда возвращайте правильные значения по умолчанию (это можно сделать)
evilkos

15

На самом деле у меня еще не было возможности попробовать это самостоятельно, но в следующий раз я настраиваю проект с большим требованием для INotifyPropertyChanged. Я намереваюсь написать атрибут Postsharp, который будет внедрять код во время компиляции. Что-то вроде:

[NotifiesChange]
public string FirstName { get; set; }

Станет:

private string _firstName;

public string FirstName
{
   get { return _firstname; }
   set
   {
      if (_firstname != value)
      {
          _firstname = value;
          OnPropertyChanged("FirstName")
      }
   }
}

Я не уверен, сработает ли это на практике, и мне нужно сесть и попробовать, но я не понимаю, почему нет. Мне может понадобиться, чтобы он принимал некоторые параметры для ситуаций, когда нужно запускать более одного OnPropertyChanged (если, например, у меня было свойство FullName в классе выше)

В настоящее время я использую собственный шаблон в Resharper, но даже после этого мне надоели все мои свойства так долго.


Ах, быстрый поиск в Google (который я должен был сделать до того, как написал это) показывает, что по крайней мере один человек сделал что-то подобное здесь раньше . Не совсем то, что я имел в виду, но достаточно близко, чтобы показать, что теория хороша.


6
Бесплатный инструмент под названием Fody, похоже, делает то же самое, функционируя как универсальный инжектор кода времени компиляции. Его можно загрузить в Nuget, как и его пакеты плагинов PropertyChanged и PropertyChanging.
Триынко

11

Да, лучший способ, безусловно, существует. Вот:

Шаг за шагом, учебник сжался мной, основываясь на этой полезной статье .

  • Создать новый проект
  • Установить основной пакет замка в проект

Install-Package Castle.Core

  • Установите только легкие библиотеки mvvm

Установочный пакет MvvmLightLibs

  • Добавьте два класса в проект:

NotifierInterceptor

public class NotifierInterceptor : IInterceptor
    {
        private PropertyChangedEventHandler handler;
        public static Dictionary<String, PropertyChangedEventArgs> _cache =
          new Dictionary<string, PropertyChangedEventArgs>();

        public void Intercept(IInvocation invocation)
        {
            switch (invocation.Method.Name)
            {
                case "add_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                case "remove_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                default:
                    if (invocation.Method.Name.StartsWith("set_"))
                    {
                        invocation.Proceed();
                        if (handler != null)
                        {
                            var arg = retrievePropertyChangedArg(invocation.Method.Name);
                            handler(invocation.Proxy, arg);
                        }
                    }
                    else invocation.Proceed();
                    break;
            }
        }

        private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
        {
            PropertyChangedEventArgs arg = null;
            _cache.TryGetValue(methodName, out arg);
            if (arg == null)
            {
                arg = new PropertyChangedEventArgs(methodName.Substring(4));
                _cache.Add(methodName, arg);
            }
            return arg;
        }
    }

ProxyCreator

public class ProxyCreator
{
    public static T MakeINotifyPropertyChanged<T>() where T : class, new()
    {
        var proxyGen = new ProxyGenerator();
        var proxy = proxyGen.CreateClassProxy(
          typeof(T),
          new[] { typeof(INotifyPropertyChanged) },
          ProxyGenerationOptions.Default,
          new NotifierInterceptor()
          );
        return proxy as T;
    }
}
  • Создайте модель вида, например:

-

 public class MainViewModel
    {
        public virtual string MainTextBox { get; set; }

        public RelayCommand TestActionCommand
        {
            get { return new RelayCommand(TestAction); }
        }

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • Поместите привязки в xaml:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • Поместите строку кода в файл с выделенным кодом MainWindow.xaml.cs следующим образом:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Наслаждаться.

введите описание изображения здесь

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


Мне интересно узнать, какую версию Castle вы используете. Я использую 3.3.0 и CreateClassProxy метод не имеет этих параметров: type, interfaces to apply, interceptors.
IAbstract

Неважно, я использовал общий CreateClassProxy<T>метод. Сильно отличается ... хм, интересно, почему так ограничен с помощью общего метода. :(
IAbstract


5

Посмотрите здесь: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

Он написан на немецком языке, но вы можете скачать ViewModelBase.cs. Все комментарии в CS-файле написаны на английском языке.

С помощью этого ViewModelBase-Class можно реализовать привязываемые свойства, подобные хорошо известным свойствам зависимости:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}

1
Ссылка не работает.
Гуге

4

Основываясь на ответе Томаса, который был адаптирован из ответа Марка, я превратил измененный код свойства отражения в базовый класс:

public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) 
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var me = selectorExpression.Body as MemberExpression;

        // Nullable properties can be nested inside of a convert function
        if (me == null)
        {
            var ue = selectorExpression.Body as UnaryExpression;
            if (ue != null)
                me = ue.Operand as MemberExpression;
        }

        if (me == null)
            throw new ArgumentException("The body must be a member expression");

        OnPropertyChanged(me.Member.Name);
    }

    protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(selectorExpression);
        foreach (var item in additonal)
            OnPropertyChanged(item);
    }
}

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

private int _quantity;
private int _price;

public int Quantity 
{ 
    get { return _quantity; } 
    set { SetField(ref _quantity, value, () => Quantity, () => Total); } 
}
public int Price 
{ 
    get { return _price; } 
    set { SetField(ref _price, value, () => Price, () => Total); } 
}
public int Total { get { return _price * _quantity; } }

У меня есть это вождение коллекции элементов, хранящихся в BindingList, представленных через DataGridView. Это избавило меня от необходимости делать ручные вызовы Refresh () в сетку.


4

Позвольте мне представить свой собственный подход под названием Yappi . Он принадлежит генераторам производных классов Runtime proxy |, добавляя новые функциональные возможности к существующему объекту или типу, например, к Dynamic Proxy Caste Project.

Это позволяет реализовать INotifyPropertyChanged один раз в базовом классе, а затем объявить производные классы в следующем стиле, все еще поддерживая INotifyPropertyChanged для новых свойств:

public class Animal:Concept
{
    protected Animal(){}
    public virtual string Name { get; set; }
    public virtual int Age { get; set; }
}

Сложность конструкции производного класса или прокси может быть скрыта за следующей строкой:

var animal = Concept.Create<Animal>.New();

И вся работа по реализации INotifyPropertyChanged может быть выполнена следующим образом:

public class Concept:INotifyPropertyChanged
{
    //Hide constructor
    protected Concept(){}

    public static class Create<TConcept> where TConcept:Concept
    {
        //Construct derived Type calling PropertyProxy.ConstructType
        public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
        //Create constructing delegate calling Constructor.Compile
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        var caller = PropertyChanged;
        if(caller!=null)
        {
            caller(this, eventArgs);
        }
    }

    //define implementation
    public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
    {
        public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
        }
        /// <summary>
        /// Overriding property setter implementation.
        /// </summary>
        /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
        /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
        /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
        /// <typeparam name="TResult">Type of property.</typeparam>
        /// <param name="property">PropertyInfo of property.</param>
        /// <returns>Delegate, corresponding to property setter implementation.</returns>
        public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            //This code called once for each declared property on derived type's initialization.
            //EventArgs instance is shared between all events for each concrete property.
            var eventArgs = new PropertyChangedEventArgs(property.Name);
            //get delegates for base calls.
            Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
            Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);

            var comparer = EqualityComparer<TResult>.Default;

            return (pthis, value) =>
            {//This code executes each time property setter is called.
                if (comparer.Equals(value, getter(pthis))) return;
                //base. call
                setter(pthis, value);
                //Directly accessing Concept's protected method.
                pthis.OnPropertyChanged(eventArgs);
            };
        }
    }
}

Он полностью безопасен для рефакторинга, не использует отражения после создания типа и достаточно быстр.


Зачем вам нужен TDeclarationпараметр типа PropertyImplementation? Конечно, вы можете найти подходящий тип для вызова (не callvirt) геттер / сеттер только с TImplementation?
Андрей Савиных

Реализация работает в большинстве случаев. Исключения: 1. Свойства переопределены с помощью «нового» ключа C #. 2. Свойства явной реализации интерфейса.
Kelqualyn

3

Все эти ответы очень хороши.

Мое решение заключается в использовании фрагментов кода для выполнения этой работы.

При этом используется простейший вызов события PropertyChanged.

Сохраните этот фрагмент и используйте его как фрагмент «fullprop».

расположение можно найти в меню «Инструменты \ Диспетчер фрагментов кода ...» в Visual Studio.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>inotifypropfull</Title>
            <Shortcut>inotifypropfull</Shortcut>
            <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
            <Description>Code snippet for property and backing field with notification</Description>
            <Author>Ofir Zeitoun</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set { 
            $field$ = value;
            var temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs("$property$"));
            }
        }
    }
    $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

Вы можете изменить вызов по своему усмотрению (использовать вышеуказанные решения)


2

Если вы используете динамику в .NET 4.5, вам не о чем беспокоиться INotifyPropertyChanged.

dynamic obj = new ExpandoObject();
obj.Name = "John";

если имя привязано к какому-либо элементу управления, оно просто отлично работает.


1
какие-либо недостатки использования этого?
ЮФО

2

Другое комбинированное решение использует StackFrame:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(ref T field, T value)
    {
        MethodBase method = new StackFrame(1).GetMethod();
        field = value;
        Raise(method.Name.Substring(4));
    }

    protected void Raise(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Применение:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}

2
Это быстро? Не связан ли доступ к фрейму стека с каким-либо требованием к разрешению? Это надежно в контексте использования async / await?
Стефан Гурихон

@ StéphaneGourichon Нет, это не так. Доступ к кадру стека означает значительное снижение производительности в большинстве случаев.
Бруно Брант

Да, вы можете увидеть это на codereview.stackexchange.com/questions/13823/…
Ofir

Обратите внимание, что встраивание может скрыть get_Fooметод в режиме Release.
bytecode77

2

Я создал метод расширения в моей базовой библиотеке для повторного использования:

public static class INotifyPropertyChangedExtensions
{
    public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
               PropertyChangedEventHandler handler, ref T field, T value, 
               [CallerMemberName] string propertyName = "",
               EqualityComparer<T> equalityComparer = null)
    {
        bool rtn = false;
        var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
        if (!eqComp.Equals(field,value))
        {
            field = value;
            rtn = true;
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(propertyName);
                handler(sender, args);
            }
        }
        return rtn;
    }
}

Это работает с .Net 4.5 из-за CallerMemberNameAttribute . Если вы хотите использовать его с более ранней версией .Net, вы должны изменить объявление метода с: ...,[CallerMemberName] string propertyName = "", ...на...,string propertyName, ...

Применение:

public class Dog : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
        }
    }
}

2

Я решил таким образом (это немного трудоемко, но, безусловно, быстрее во время выполнения).

В VB (извините, но я думаю, что это не сложно перевести на C #), я делаю эту замену с помощью RE:

(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)

с:

Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n

Это transofrm весь код, как это:

<Bindable(True)>
Protected Friend Property StartDate As DateTime?

В

Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
    Get
        Return _StartDate
    End Get
    Set(Value As DateTime?)
        If _StartDate <> Value Then
            _StartDate = Value
            RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
        End If
    End Set
End Property

И если я хочу иметь более читаемый код, я могу быть наоборот, просто сделав следующую замену:

Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property

С

${Attr} ${Def} ${Name} As ${Type}

Я бросаю, чтобы заменить код IL метода set, но я не могу написать много скомпилированного кода в IL ... Если я напишу день, я скажу вам!


2

Я держу это как фрагмент. C # 6 добавляет хороший синтаксис для вызова обработчика.

// INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(property, value) == false)
    {
        property = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2

Вот версия NotifyPropertyChanged для Unity3D или не CallerMemberName

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> property)
    {
        var expression = (MemberExpression) property.Body;
        var propertyName = expression.Member.Name;

        Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
        return propertyName;
    }

    #region Notification Handlers

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

Этот код позволяет вам писать поля поддержки свойств следующим образом:

  public string Text
    {
        get { return Get<string>(); }
        set { Set(value); }
    }

Кроме того, в resharper, если вы создадите фрагмент шаблона / поиска, вы также можете автоматизировать рабочий процесс, преобразовав простые поля проп в вышеуказанную основу.

Шаблон поиска:

public $type$ $fname$ { get; set; }

Заменить шаблон:

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}

2

Я написал статью, которая помогает с этим ( https://msdn.microsoft.com/magazine/mt736453 ). Вы можете использовать пакет NuSet SolSoft.DataBinding. Тогда вы можете написать код так:

public class TestViewModel : IRaisePropertyChanged
{
  public TestViewModel()
  {
    this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
  }

  private readonly NotifyProperty<string> m_nameProperty;
  public string Name
  {
    get
    {
      return m_nameProperty.Value;
    }
    set
    {
      m_nameProperty.SetValue(value);
    }
  }

  // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}

Льготы:

  1. базовый класс не является обязательным
  2. не задумываться о каждом «установленном значении»
  3. может иметь свойства, которые зависят от других свойств, и все они автоматически вызывают соответствующие события (в статье есть пример этого)

2

Хотя, очевидно, существует множество способов сделать это, за исключением магических ответов AOP, ни один из ответов, похоже, не рассматривает установку свойства Model непосредственно из модели представления без наличия локального поля для ссылки.

Проблема в том, что вы не можете ссылаться на недвижимость. Однако вы можете использовать Action для установки этого свойства.

protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
    {
        return false;
    }

    property(newValue);
    RaisePropertyChanged(propertyName);
    return true;
}

Это можно использовать как следующий фрагмент кода.

public int Prop {
    get => model.Prop;
    set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}

Ознакомьтесь с этим репозиторием BitBucket для полной реализации метода и нескольких различных способов достижения того же результата, включая метод, который использует LINQ, и метод, который использует отражение. Обратите внимание, что эти методы медленнее по производительности.


1

Другие вещи, которые вы, возможно, захотите учесть при реализации этих видов свойств, это то, что INotifyPropertyChang * ed *ing оба используют классы аргументов события.

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

Взгляните на эту реализацию и объясните, почему она была задумана.

Блог Джоша Смита


1

Я только что нашел ActiveSharp - автоматический INotifyPropertyChanged , я еще не использовал его, но выглядит хорошо.

Цитировать с его веб-сайта ...


Отправлять уведомления об изменении свойства без указания имени свойства в виде строки.

Вместо этого напишите такие свойства:

public int Foo
{
    get { return _foo; }
    set { SetValue(ref _foo, value); }  // <-- no property name here
}

Обратите внимание, что нет необходимости включать имя свойства в виде строки. ActiveSharp надежно и правильно вычисляет это для себя. Это работает на основе того факта, что ваша реализация свойства передает поле поддержки (_foo) по ссылке. (ActiveSharp использует этот вызов «по ссылке», чтобы определить, какое поле поддержки было передано, и из поля оно идентифицирует свойство).


1

Идея с использованием отражения:

class ViewModelBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {

        // Get Name of Property
        string name = mb.Name.Substring(4);

        // Detect Change
        bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);

        // Return if no change
        if (!changed) return false;

        // Update value
        oldValue = newValue;

        // Raise Event
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }//if

        // Notify caller of change
        return true;

    }//method

    string name;

    public string Name {
        get { return name; }
        set {
            Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
        }
    }//method

}//class

Это довольно круто, мне нравится больше, чем выражение. С другой стороны, должно быть медленнее.
Nawfal

1

Я понимаю, что на этот вопрос уже есть миллиард ответов, но ни один из них не показался мне вполне подходящим. Моя проблема в том, что я не хочу никаких хитов производительности и готов мириться с небольшим многословием только по этой причине. Я также не слишком беспокоюсь об автоматических свойствах, что привело меня к следующему решению:

public abstract class AbstractObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
    {
        //Set value if the new value is different from the old
        if (!Source.Equals(NewValue))
        {
            Source = NewValue;

            //Notify all applicable properties
            foreach (var i in Notify)
                OnPropertyChanged(i);

            return true;
        }

        return false;
    }

    public AbstractObject()
    {
    }
}

Другими словами, вышеприведенное решение удобно, если вы не возражаете против этого:

public class SomeObject : AbstractObject
{
    public string AnotherProperty
    {
        get
        {
            return someProperty ? "Car" : "Plane";
        }
    }

    bool someProperty = false;
    public bool SomeProperty
    {
        get
        {
            return someProperty;
        }
        set
        {
            SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
        }
    }

    public SomeObject() : base()
    {
    }
}

Pros

  • Нет отражения
  • Уведомляет только если старое значение! = Новое значение
  • Уведомить несколько свойств одновременно

Cons

  • Нет автоматических свойств (вы можете добавить поддержку для обоих, хотя!)
  • Немного многословия
  • Бокс (небольшой удар по производительности?)

Увы, это все же лучше, чем делать это,

set
{
    if (!someProperty.Equals(value))
    {
        someProperty = value;
        OnPropertyChanged("SomeProperty");
        OnPropertyChanged("AnotherProperty");
    }
}

Для каждого свойства, которое становится кошмаром с дополнительным многословием ;-(

Обратите внимание, я не утверждаю, что это решение лучше с точки зрения производительности по сравнению с другими, просто это жизнеспособное решение для тех, кому не нравятся другие представленные решения.


1

Я придумал этот базовый класс для реализации наблюдаемого паттерна, который в значительной степени делает то, что вам нужно ( «автоматически» реализуя set и get). Я потратил на это целый час в качестве прототипа, поэтому он не имеет много модульных тестов, но подтверждает концепцию. Обратите внимание, что он использует Dictionary<string, ObservablePropertyContext>для удаления необходимости в закрытых полях.

  public class ObservableByTracking<T> : IObservable<T>
  {
    private readonly Dictionary<string, ObservablePropertyContext> _expando;
    private bool _isDirty;

    public ObservableByTracking()
    {
      _expando = new Dictionary<string, ObservablePropertyContext>();

      var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
      foreach (var property in properties)
      {
        var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType)
        {
          Value = GetDefault(property.PropertyType)
        };

        _expando[BuildKey(valueContext)] = valueContext;
      }
    }

    protected void SetValue<T>(Expression<Func<T>> expression, T value)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var originalValue = (T)_expando[key].Value;
      if (EqualityComparer<T>.Default.Equals(originalValue, value))
      {
        return;
      }

      _expando[key].Value = value;
      _isDirty = true;
    }

    protected T GetValue<T>(Expression<Func<T>> expression)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var value = _expando[key].Value;
      return (T)value;
    }

    private KeyContext GetKeyContext<T>(Expression<Func<T>> expression)
    {
      var castedExpression = expression.Body as MemberExpression;
      if (castedExpression == null)
      {
        throw new Exception($"Invalid expression.");
      }

      var parameterName = castedExpression.Member.Name;

      var propertyInfo = castedExpression.Member as PropertyInfo;
      if (propertyInfo == null)
      {
        throw new Exception($"Invalid expression.");
      }

      return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName};
    }

    private static string BuildKey(ObservablePropertyContext observablePropertyContext)
    {
      return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}";
    }

    private static string BuildKey(string parameterName, Type type)
    {
      return $"{type.Name}.{parameterName}";
    }

    private static object GetDefault(Type type)
    {
      if (type.IsValueType)
      {
        return Activator.CreateInstance(type);
      }
      return null;
    }

    public bool IsDirty()
    {
      return _isDirty;
    }

    public void SetPristine()
    {
      _isDirty = false;
    }

    private class KeyContext
    {
      public string PropertyName { get; set; }
      public Type PropertyType { get; set; }
    }
  }

  public interface IObservable<T>
  {
    bool IsDirty();
    void SetPristine();
  }

Вот использование

public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
  {
    public ObservableByTrackingTestClass()
    {
      StringList = new List<string>();
      StringIList = new List<string>();
      NestedCollection = new List<ObservableByTrackingTestClass>();
    }

    public IEnumerable<string> StringList
    {
      get { return GetValue(() => StringList); }
      set { SetValue(() => StringIList, value); }
    }

    public IList<string> StringIList
    {
      get { return GetValue(() => StringIList); }
      set { SetValue(() => StringIList, value); }
    }

    public int IntProperty
    {
      get { return GetValue(() => IntProperty); }
      set { SetValue(() => IntProperty, value); }
    }

    public ObservableByTrackingTestClass NestedChild
    {
      get { return GetValue(() => NestedChild); }
      set { SetValue(() => NestedChild, value); }
    }

    public IList<ObservableByTrackingTestClass> NestedCollection
    {
      get { return GetValue(() => NestedCollection); }
      set { SetValue(() => NestedCollection, value); }
    }

    public string StringProperty
    {
      get { return GetValue(() => StringProperty); }
      set { SetValue(() => StringProperty, value); }
    }
  }

1

Я предлагаю использовать ReactiveProperty. Это самый короткий метод, кроме Fody.

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    ...
    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

вместо

public class Data
{
    // Don't need boiler-plate and INotifyPropertyChanged

    // props
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
}

( DOCS )


0

Другая идея ...

 public class ViewModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
    protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
        _propertyStore[propertyName] = value;
        OnPropertyChanged(propertyName);
    }
    protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        object ret;
        if (_propertyStore.TryGetValue(propertyName, out ret))
        {
            return (T)ret;
        }
        else
        {
            return default(T);
        }
    }

    //Usage
    //public string SomeProperty {
    //    get { return GetValue<string>();  }
    //    set { SetValue(value); }
    //}

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

0

=> здесь мое решение со следующими функциями

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. нет рефлексии
  2. краткая запись
  3. нет волшебной строки в вашем бизнес-коде
  4. Возможность повторного использования PropertyChangedEventArgs в приложении
  5. Возможность уведомить несколько свойств в одном заявлении

0

Использовать этот

using System;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;


public static class ObservableFactory
{
    public static T Create<T>(T target)
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Target should be an interface", "target");

        var proxy = new Observable<T>(target);
        return (T)proxy.GetTransparentProxy();
    }
}

internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
{
    private readonly T target;

    internal Observable(T target)
        : base(ImplementINotify(typeof(T)))
    {
        this.target = target;
    }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;

        if (methodCall != null)
        {
            return HandleMethodCall(methodCall);
        }

        return null;
    }

    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;



    IMessage HandleMethodCall(IMethodCallMessage methodCall)
    {
        var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
        var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;

        if (isPropertySetterCall)
        {
            OnPropertyChanging(propertyName);
        }

        try
        {
            object methodCalltarget = target;

            if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
                methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
            {
                methodCalltarget = this;
            }

            var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);

            if (isPropertySetterCall)
            {
                OnPropertyChanged(methodCall.MethodName.Substring(4));
            }

            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (TargetInvocationException invocationException)
        {
            var exception = invocationException.InnerException;
            return new ReturnMessage(exception, methodCall);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanging(string propertyName)
    {
        var handler = PropertyChanging;
        if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
    }

    public static Type ImplementINotify(Type objectType)
    {
        var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());

        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);

        var moduleBuilder = dynamicAssembly.DefineDynamicModule(
            tempAssemblyName.Name,
            tempAssemblyName + ".dll");

        var typeBuilder = moduleBuilder.DefineType(
            objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);

        typeBuilder.AddInterfaceImplementation(objectType);
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
        var newType = typeBuilder.CreateType();
        return newType;
    }
}

}

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.