Implementing INotifyPropertyChanged - does a better way exist?

前端 未结 30 2614
感情败类
感情败类 2020-11-21 05:23

Microsoft should have implemented something snappy for INotifyPropertyChanged, like in the automatic properties, just specify {get; set; notify;} I

相关标签:
30条回答
  • 2020-11-21 05:29

    As of .Net 4.5 there is finally an easy way to do this.

    .Net 4.5 introduces a new Caller Information Attributes.

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

    It's probably a good idea to add a comparer to the function as well.

    EqualityComparer<T>.Default.Equals
    

    More examples here and here

    Also see Caller Information (C# and Visual Basic)

    0 讨论(0)
  • 2020-11-21 05:29

    I suggest to use ReactiveProperty. This is the shortest method except Fody.

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

    instead

    public class Data
    {
        // Don't need boiler-plate and INotifyPropertyChanged
    
        // props
        public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
    }
    

    (DOCS)

    0 讨论(0)
  • 2020-11-21 05:30

    Whilst there are obviously lots of ways to do this, with the exception of the AOP magic answers, none of the answers seem to look at setting a Model's property directly from the view model without having a local field to reference.

    The issue is you can't reference a property. However, you can use an Action to set that property.

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

    This can be used like the following code extract.

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

    Check out this BitBucket repo for a full implementation of the method and a few different ways of achieving the same result, including a method that uses LINQ and a method that uses reflection. Do note that these methods are slower performance wise.

    0 讨论(0)
  • 2020-11-21 05:30

    Other things you may want to consider when implementing these sorts of properties is the fact that the INotifyPropertyChang *ed *ing both use event argument classes.

    If you have a large number of properties that are being set then the number of event argument class instances can be huge, you should consider caching them as they are one of the areas that a string explosion can occur.

    Take a look at this implementation and explanation of why it was conceived.

    Josh Smiths Blog

    0 讨论(0)
  • 2020-11-21 05:32

    => here my solution with the following features

     public ResourceStatus Status
     {
         get { return _status; }
         set
         {
             _status = value;
             Notify(Npcea.Status,Npcea.Comments);
         }
     }
    
    1. no refelction
    2. short notation
    3. no magic string in your business code
    4. Reusability of PropertyChangedEventArgs across application
    5. Possibility to notify multiple properties in one statement
    0 讨论(0)
  • 2020-11-21 05:33

    Based on the answer by Thomas which was adapted from an answer by Marc I've turned the reflecting property changed code into a base class:

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

    Usage is the same as Thomas' answer except that you can pass additional properties to notify for. This was necessary to handle calculated columns which need to be refreshed in a grid.

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

    I have this driving a collection of items stored in a BindingList exposed via a DataGridView. It has eliminated the need for me to do manual Refresh() calls to the grid.

    0 讨论(0)
提交回复
热议问题