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

563

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

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

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

  • 0
    см. stackoverflow.com/questions/1329138/… для проверенного компилятором способа реализации INotifyPropertyChanged. Избегайте использования имен свойств в качестве магической строки.
  • 6
    code.google.com/p/notifypropertyweaver может быть полезным
Показать ещё 9 комментариев
Теги:
winforms
inotifypropertychanged

32 ответа

513
Лучший ответ

Не используя что-то вроде 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 указывает, был ли он нерабочим, если вы хотите применить другую логику.


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

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

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

set { SetField(ref name, value); }

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


С# 6.0 упрощает реализацию:

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

... и теперь с С# 7:

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}
  • 4
    Хороший трюк Марк! Я предложил улучшить использование лямбда-выражения вместо имени свойства, см. Мой ответ
  • 3
    DevXpress Xpo делает это таким образом.
Показать ещё 38 комментариев
175

По сравнению с .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

Дополнительные примеры здесь и здесь

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

  • 2
    Самый превосходный. Это определенно, кажется, способ двигаться вперед.
  • 11
    Brilliant! Но почему это общее?
Показать ещё 9 комментариев
152

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

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

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

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;
}

BTW, это было вдохновлено этот пост в блоге обновленный URL

  • 6
    Есть по крайней мере одна структура, использующая этот метод, ReactiveUI .
  • 0
    Очень поздно, это означало пройти через рефлексию, что означало удар по производительности. Это может быть приемлемо, но установка свойства - это не то место, где я бы хотел, чтобы мое приложение проводило много циклов.
Показать ещё 4 комментария
95

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

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

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

  • 9
    Я сразу перешел от принятого решения к ответу на этот вопрос после некоторого гугла.
  • 0
    Спасло меня как минимум за неделю опечаток и работает безупречно, даже с ObservableCollections.
Показать ещё 6 комментариев
60

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

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


Вот загляните в результат Изображение 1034

  • 10
    -1: производительность не снижается: CallerMemberName изменяются на литеральные значения во время компиляции. Просто попробуйте декомпилировать ваше приложение.
  • 0
    вот соответствующий вопрос и ответ: stackoverflow.com/questions/22580623/…
Показать ещё 4 комментария
31

В моем блоге я представляю класс 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); }
    }
}
  • 1
    Мне это и вправду нравится! У меня была такая же идея со словарем, и я начал программировать. Я хотел опубликовать это и прокрутил вниз и увидел ваш ответ! Чуть лучше, чем у меня. Ницца! :)
  • 2
    Это хорошее решение, но единственным недостатком является то, что есть небольшой удар по производительности, связанный с боксом / распаковкой.
Показать ещё 6 комментариев
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.
9

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

Пошаговое руководство сократилось мной на основе этой полезной статьи.

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

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>();

  • Наслаждайтесь.

Изображение 1035

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

  • 0
    Это именно то, что я искал. Спасибо!!!
  • 0
    Мне интересно узнать, какую версию Castle вы используете. Я использую 3.3.0 и метод CreateClassProxy не имеет этих параметров: type , interfaces to apply , interceptors .
Показать ещё 1 комментарий
6

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

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

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

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}
  • 1
    Ссылка не работает.
6

Очень похожий на AOP подход заключается в том, чтобы вводить материал INotifyPropertyChanged на уже созданный объект на лету. Вы можете сделать это с помощью чего-то вроде Castle DynamicProxy. Вот статья, которая объясняет технику:

Добавление INotifyPropertyChanged к существующему объекту

4

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

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 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 protected method.
                pthis.OnPropertyChanged(eventArgs);
            };
        }
    }
}

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

  • 0
    Зачем вам нужен параметр типа TDeclaration в PropertyImplementation ? Конечно, вы можете найти подходящий тип для вызова (не callvirt) геттер / сеттер только с TImplementation ?
  • 0
    Реализация работает в большинстве случаев. Исключения составляют: 1. Свойства, переопределенные с помощью «нового» ключа C #. 2. Свойства явной реализации интерфейса.
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

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

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)
}

Преимущества:

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

Вот версия Unity3D или Non-CallerMemberName NotifyPropertyChanged

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 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 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); }
    }

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

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

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

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

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

Я держу это как фрагмент. С# 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

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

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);
        }
    }
}
  • 0
    Ницца. Это экономит много повторяющегося кода. +1
2

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

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

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

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

Я придумал этот базовый класс для реализации наблюдаемого шаблона, в значительной степени делает то, что вам нужно ( "автоматически" реализует набор и 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

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

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

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

против

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

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

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

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

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

1

Еще одно комбинированное решение использует 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?
  • 0
    @ StéphaneGourichon Нет, это не так. Доступ к кадру стека означает значительное снижение производительности в большинстве случаев.
Показать ещё 1 комментарий
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
  • 0
    Это довольно круто, мне нравится больше, чем выражение. С другой стороны, должно быть медленнее.
1

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

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

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

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

0

Я предлагаю использовать 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

Призма 5:

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

    protected virtual bool SetProperty<T>(ref T storage,
                                          T value,
                                          [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);

        return true;
    }

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

    protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
        this.OnPropertyChanged(propertyName);
    }
}

public static class PropertySupport
{
    public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        if (propertyExpression == null)
        {
            throw new ArgumentNullException("propertyExpression");
        }

        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
        }

        var property = memberExpression.Member as PropertyInfo;
        if (property == null)
        {
            throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
        }

        var getMethod = property.GetMethod;
        if (getMethod.IsStatic)
        {
            throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
        }

        return memberExpression.Member.Name;
    }
}
0

Я использую следующий метод расширения (используя С# 6.0), чтобы сделать реализацию INPC максимально простой:

public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    if (comparer == null)
        comparer = EqualityComparer<T>.Default;

    if (comparer.Equals(field, value))
    {
        return false;
    }
    else
    {
        field = value;
        propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
        return true;
    }
}

Реализация INPC сводится к (вы можете либо реализовать это каждый раз, либо создать базовый класс):

public class INPCBaseClass: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool changeProperty<T>(ref T field, T value,
        IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
    {
        return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName);
    }
}

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

private string testProperty;
public string TestProperty
{
    get { return testProperty; }
    set { changeProperty(ref testProperty, value); }
}

ПРИМЕЧАНИЕ. Вы можете опустить объявление [CallerMemberName] в методе расширения, если хотите, но я хотел бы сохранить его гибким.

Если у вас есть свойства без поля поддержки, вы можете перегрузить changeProperty:

protected bool changeProperty<T>(T property, Action<T> set, T value,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    bool ret = changeProperty(ref property, value, comparer, propertyName);
    if (ret)
        set(property);
    return ret;
}

Пример использования:

public string MyTestProperty
{
    get { return base.TestProperty; }
    set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); }
}
0

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

В VB (извините, но я думаю, что это не сложно перевести его на С#), я делаю эту замену 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

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

<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-код установленного метода, но я не могу написать много скомпилированного кода в IL... Если в день я его напишу, я скажу вам!

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;
    }
}

}

0

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

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  • не refelction
  • короткая нотация
  • Никакая волшебная строка в вашем бизнес-коде
  • Возможность повторного использования PropertyChangedEventArgs в приложении
  • Возможность уведомления нескольких свойств в одном выражении
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

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

Процитировать с веб-сайта...


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

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

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

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

-3

Разговор о массовом перестройке. Это значительно сложнее, чем просто правильно это делает и дает мало пользы. Если ваша среда IDE поддерживает фрагменты кода (Visual Studio/MonoDevelop do), вы можете сделать реализацию этой смехотворно простой. Все, что вам нужно набрать, это тип свойства и имя свойства. Дополнительные три строки кода будут автогенерированы.

  • 0
    Чтобы держаться подальше от волшебных струн, вы также можете использовать код из этого поста в блоге: blog.m.jedynak.pl/2009/02/static-typed-propety-names.html.
  • 12
    Фрагменты кода хороши, когда вы пишете код, но могут стать проблемой при обслуживании.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню