Implementing INotifyPropertyChanged - does a better way exist?

前端 未结 30 2471
感情败类
感情败类 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:46

    I'm writing a library that deals with INotifyPropertyChanged, and the main idea is using a dynamic proxy to notify changes.

    the repo is here: CaulyKan/NoMorePropertyChanged

    with this library, you can write:

        public dynamic Test1Binding { get; set; }
        public TestDTO Test1
        {
            get { return (TestDTO)Test1Binding; }
            set { SetBinding(nameof(Test1Binding), value); }
        }
    

    Then make all bindings and modifications go to Test1Binding, which will notify PropertyChange and CollectionChanged automatically no matter how complex TestDTO is.

    it can also handle dependencies.

        [DependsOn("Test1Binding.TestString")]
        public string Test2
        {
            get { return Test1Binding.TestString; }
        }
    

    Please give me some suggestions.

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

    Another Idea...

     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 讨论(0)
  • 2020-11-21 05:47

    I resolved in This Way (it's a little bit laboriouse, but it's surely the faster in runtime).

    In VB (sorry, but I think it's not hard translate it in C#), I make this substitution with RE:

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

    with:

    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
    

    This transofrm all code like this:

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

    In

    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
    

    And If I want to have a more readable code, I can be the opposite just making the following substitution:

    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
    

    With

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

    I throw to replace the IL code of the set method, but I can't write a lot of compiled code in IL... If a day I write it, I'll say you!

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

    A very AOP-like approach is to inject the INotifyPropertyChanged stuff onto an already instantiated object on the fly. You can do this with something like Castle DynamicProxy. Here is an article that explains the technique:

    Adding INotifyPropertyChanged to an existing object

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

    There's also Fody which has a PropertyChanged add-in, which lets you write this:

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

    ...and at compile time injects property changed notifications.

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

    I introduce a Bindable class in my blog at http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ Bindable uses a dictionary as a property bag. It's easy enough to add the necessary overloads for a subclass to manage its own backing field using ref parameters.

    • No magic string
    • No reflection
    • Can be improved to suppress the default dictionary lookup

    The code:

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

    It can be used like this:

    public class Contact : Bindable {
        public string FirstName {
            get { return Get<string>(); }
            set { Set(value); }
        }
    }
    
    0 讨论(0)
提交回复
热议问题