首页 文章

自动INotifyPropertyChanged

提问于
浏览
53

有没有办法自动获得类中属性更改的通知,而无需在每个setter中编写OnPropertyChanged? (如果有更改,我有数百个我想知道的属性) .


安东建议dynamic proxies . 我必须写,它在我的程序启动时间(ymmv)增加了大约30秒 - 因为它是一个运行时解决方案 .

我想知道是否有编译时解决方案,可能使用编译时属性...


Slashene和TcKs给出了产生重复代码的建议 - 遗憾的是,并非所有属性都是m_Value = value的简单情况 - 很多都在setter中有自定义代码,所以来自代码片段和xml的cookie-cutter代码实际上不可行我的项目也是 .

13 回答

  • 10

    EDIT: NotifyPropertyWeaver的作者已弃用该工具,转而采用更为通用的Fody . (对于从织布 Worker 到战士的人,可以使用migration guide . )


    我用于项目的一个非常方便的工具是Notify Property Weaver Fody .

    它将自身安装为项目中的构建步骤,并在编译期间注入引发 PropertyChanged 事件的代码 .

    使属性提升PropertyChanged是通过将special attributes放在它们上来完成的:

    [ImplementPropertyChanged]
    public string MyProperty { get; set; }
    

    作为奖励,您还可以指定依赖于其他属性的属性的关系

    [ImplementPropertyChanged]
    public double Radius { get; set; }
    
    [DependsOn("Radius")]
    public double Area 
    {
        get { return Radius * Radius * Math.PI; }
    }
    
  • -3

    nameof运算符是在2015年7月使用.NET 4.6和VS2015在C#6.0中实现的 . 以下内容对C#<6.0仍然有效

    我们使用下面的代码(来自http://www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx) . 很棒:)

    public static class NotificationExtensions
    {
        #region Delegates
    
        /// <summary>
        /// A property changed handler without the property name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sender">The object that raised the event.</param>
        public delegate void PropertyChangedHandler<TSender>(TSender sender);
    
        #endregion
    
        /// <summary>
        /// Notifies listeners about a change.
        /// </summary>
        /// <param name="EventHandler">The event to raise.</param>
        /// <param name="Property">The property that changed.</param>
        public static void Notify(this PropertyChangedEventHandler EventHandler, Expression<Func<object>> Property)
        {
            // Check for null
            if (EventHandler == null)
                return;
    
            // Get property name
            var lambda = Property as LambdaExpression;
            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = lambda.Body as UnaryExpression;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }
            else
            {
                memberExpression = lambda.Body as MemberExpression;
            }
    
            ConstantExpression constantExpression;
            if (memberExpression.Expression is UnaryExpression)
            {
                var unaryExpression = memberExpression.Expression as UnaryExpression;
                constantExpression = unaryExpression.Operand as ConstantExpression;
            }
            else
            {
                constantExpression = memberExpression.Expression as ConstantExpression;
            }
    
            var propertyInfo = memberExpression.Member as PropertyInfo;
    
            // Invoke event
            foreach (Delegate del in EventHandler.GetInvocationList())
            {
                del.DynamicInvoke(new[]
                {
                    constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name)
                });
            }
        }
    
    
        /// <summary>
        /// Subscribe to changes in an object implementing INotifiyPropertyChanged.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ObjectThatNotifies">The object you are interested in.</param>
        /// <param name="Property">The property you are interested in.</param>
        /// <param name="Handler">The delegate that will handle the event.</param>
        public static void SubscribeToChange<T>(this T ObjectThatNotifies, Expression<Func<object>> Property, PropertyChangedHandler<T> Handler) where T : INotifyPropertyChanged
        {
            // Add a new PropertyChangedEventHandler
            ObjectThatNotifies.PropertyChanged += (s, e) =>
                {
                    // Get name of Property
                    var lambda = Property as LambdaExpression;
                    MemberExpression memberExpression;
                    if (lambda.Body is UnaryExpression)
                    {
                        var unaryExpression = lambda.Body as UnaryExpression;
                        memberExpression = unaryExpression.Operand as MemberExpression;
                    }
                    else
                    {
                        memberExpression = lambda.Body as MemberExpression;
                    }
                    var propertyInfo = memberExpression.Member as PropertyInfo;
    
                    // Notify handler if PropertyName is the one we were interested in
                    if (e.PropertyName.Equals(propertyInfo.Name))
                    {
                        Handler(ObjectThatNotifies);
                    }
                };
        }
    }
    

    例如这样使用:

    public class Employee : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private string _firstName;
        public string FirstName
        {
            get { return this._firstName; }
            set
            {
                this._firstName = value;
                this.PropertyChanged.Notify(()=>this.FirstName);
            }
        }
    }
    
    private void firstName_PropertyChanged(Employee sender)
    {
        Console.WriteLine(sender.FirstName);
    }
    
    employee = new Employee();
    employee.SubscribeToChange(() => employee.FirstName, firstName_PropertyChanged);
    

    示例中可能存在一些语法错误 . 没试过 . 但你应该至少有这个概念:)

    EDIT: 我现在看到你可能想要更少的工作,但是......上面的东西至少使它变得容易多了 . 并且使用字符串引用属性可以防止所有可怕的问题 .

  • 4

    Framework 4.5为我们提供了CallerMemberNameAttribute,这使得将属性名称作为字符串传递是不必要的:

    private string m_myProperty;
    public string MyProperty
    {
        get { return m_myProperty; }
        set
        {
            m_myProperty = value;
            OnPropertyChanged();
        }
    }
    
    private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
    {
        // ... do stuff here ...
    }
    

    类似于Svish的解决方案,只需用无聊的框架功能取代lambda awesomeness ;-)

    如果您正在使用安装了KB2468871的Framework 4.0,则可以通过nuget安装Microsoft BCL兼容包,它也提供此属性 .

  • 4

    您可以在PropertyChanged委托上使用扩展方法并像这样使用它:

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            PropertyChanged.Raise(() => Name);
        }
    }
    

    订阅特定房产变更:

    var obj = new Employee();
    
    var handler = obj.SubscribeToPropertyChanged(
        o => o.FirstName, 
        o => Console.WriteLine("FirstName is now '{0}'", o.FirstName));
    
    obj.FirstName = "abc";
    
    // Unsubscribe when required
    obj.PropertyChanged -= handler;
    

    扩展方法只能通过检查lambda表达式树来确定发送方和属性名称,而不会对性能产生重大影响:

    public static class PropertyChangedExtensions
    {
        public static void Raise<TProperty>(
            this PropertyChangedEventHandler handler, Expression<Func<TProperty>> property)
        {
            if (handler == null)
                return;
    
            var memberExpr = (MemberExpression)property.Body;
            var propertyName = memberExpr.Member.Name;
            var sender = ((ConstantExpression)memberExpr.Expression).Value;
            handler.Invoke(sender, new PropertyChangedEventArgs(propertyName));
        }
    
        public static PropertyChangedEventHandler SubscribeToPropertyChanged<T, TProperty>(
            this T obj, Expression<Func<T, TProperty>> property, Action<T> handler)
            where T : INotifyPropertyChanged
        {
            if (handler == null)
                return null;
    
            var memberExpr = (MemberExpression)property.Body;
            var propertyName = memberExpr.Member.Name;
    
            PropertyChangedEventHandler subscription = (sender, eventArgs) =>
            {
                if (propertyName == eventArgs.PropertyName)
                    handler(obj);
            };
    
            obj.PropertyChanged += subscription;
    
            return subscription;
        }
    }
    

    如果 PropertyChanged 事件在基类型中声明,则它将不会在派生类中作为委托字段显示 . 在这种情况下,解决方法是声明 PropertyChangedEventHandler 类型的受保护字段并显式实现事件的 addremove 访问者:

    public class Base : INotifyPropertyChanged
    {
        protected PropertyChangedEventHandler propertyChanged;
        public event PropertyChangedEventHandler PropertyChanged
        {
            add { propertyChanged += value; }
            remove { propertyChanged -= value; }
        }
    }
    
    public class Derived : Base
    {
        string name;
    
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                propertyChanged.Raise(() => Name);
            }
        }
    }
    
  • 2

    实现类型安全 INotifyPropertyChangedSee here

    然后制作自己的代码段:

    private $Type$ _$PropertyName$;
    public $Type$ $PropertyName$
    {
        get
        {
            return _$PropertyName$;
        }
        set
        {
            if(value != _$PropertyName$)
            {
                _$PropertyName$ = value;
                OnPropertyChanged(o => o.$PropertyName$);               
            }
        }
    }
    

    随着Code snippet designer而你已经完成了!简单,安全的方式来 Build 您的INotifyPropertyChanged .

  • 10

    我不知道没有标准的方法,但我知道两个解决方法:

    1)PostSharp可以在编译后为你做 . 它非常有用,但每次构建都需要一些时间 .

    2)自定义工具i Visual Studio . 您可以将其与“部分类”结合使用 . 然后,您可以为XML创建自定义工具,并且可以从xml生成源代码 .

    例如这个xml:

    <type scope="public" type="class" name="MyClass">
        <property scope="public" type="string" modifier="virtual" name="Text" notify="true" />
    </type>
    

    可以是此代码的来源:

    public partial class MyClass {
        private string _text;
        public virtual string Text {
            get { return this._Text; }
            set {
                this.OnPropertyChanging( "Text" );
                this._Text = value;
                this.OnPropertyChanged( "Text" );
            }
        }
    }
    
  • 1

    您可能希望从整体上考虑面向方面编程

    框架=>你可以看linfu

  • 28

    您可以查看Castle或Spring.NET并实现拦截器功能吗?

  • 37

    改进儿童课程中的活动:

    感谢:this.NotifyPropertyChange(()=> PageIndex);

    在NotificationExtensions类中添加:

    /// <summary>
        /// <para>Lève l'évènement de changement de valeur sur l'objet <paramref name="sender"/>
        /// pour la propriété utilisée dans la lambda <paramref name="property"/>.</para>
        /// </summary>
        /// <param name="sender">L'objet portant la propriété et l'évènement.</param>
        /// <param name="property">Une expression lambda utilisant la propriété subissant la modification.</param>
        public static void NotifyPropertyChange(this INotifyPropertyChanged sender, Expression<Func<Object>> property)
        {
            if (sender == null)
                return;
    
            // Récupère le nom de la propriété utilisée dans la lambda en argument
            LambdaExpression lambda = property as LambdaExpression;
            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                UnaryExpression unaryExpression = lambda.Body as UnaryExpression;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }
            else
            {
                memberExpression = lambda.Body as MemberExpression;
            }
            ConstantExpression constantExpression = memberExpression.Expression as ConstantExpression;
            PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo;
    
    
            // il faut remonter la hierarchie, car meme public, un event n est pas visible dans les enfants
            FieldInfo eventField;
            Type baseType = sender.GetType();
            do
            {
                eventField = baseType.GetField(INotifyPropertyChangedEventFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
                baseType = baseType.BaseType;
            } while (eventField == null);
    
            // on a trouvé l'event, on peut invoquer tt les delegates liés
            MulticastDelegate eventDelegate = eventField.GetValue(sender) as MulticastDelegate;
            if (eventDelegate == null) return; // l'event n'est bindé à aucun delegate
            foreach (Delegate handler in eventDelegate.GetInvocationList())
            {
                handler.Method.Invoke(handler.Target, new Object[] { sender, new PropertyChangedEventArgs(propertyInfo.Name) });
            }
        }
    
  • 1

    我刚刚发现ActiveSharp - Automatic INotifyPropertyChanged,我还没有使用它,但它看起来不错 .

    引用它的网站......


    发送属性更改通知,而不指定属性名称作为字符串 .

    相反,写这样的属性:

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

    请注意,不需要将属性的名称包含在字符串中 . ActiveSharp可靠而正确地为自己确定了这一点 . 它的工作原理是您的属性实现通过了支持字段(_foo)由ref . (ActiveSharp使用“by ref”调用来标识传递了哪个支持字段,并从字段中识别属性) .

  • 1

    只是为了实现 quicker ,你可以使用一个片段

    http://aaron-hoffman.blogspot.it/2010/09/visual-studio-code-snippet-for-notify.html

    遵循M-V-VM模式的ViewModel项目类通常需要从属性的setter中引发“PropertyChanged”事件(以协助INotifyPropertyChanged接口实现) . 这是一项繁琐的任务,希望有一天能够通过使用编译器即服务来解决...

    片段核心( full credit 给作者,不是我的)是以下内容

    <Code Language= "csharp "> 
        <![CDATA[public $type$ $property$ 
    { 
        get { return _$property$; } 
        set 
        { 
            if (_$property$ != value) 
            { 
                _$property$ = value; 
                OnPropertyChanged($property$PropertyName); 
            } 
        } 
    } 
    private $type$ _$property$; 
    public const string $property$PropertyName = "$property$";$end$]]> 
    </Code>
    
  • 1

    Property Changed没有Single实现可以处理人们想要使用它的各种方式 . 最好的办法是生成一个帮助类来为你做这项工作,这是我使用的一个例子

    /// <summary>
    /// Helper Class that automates most of the actions required to implement INotifyPropertyChanged
    /// </summary>
    public static class HPropertyChanged
    {
        private static Dictionary<string, PropertyChangedEventArgs> argslookup = new Dictionary<string, PropertyChangedEventArgs>();
        public static string ThisPropertyName([CallerMemberName]string name = "")
        {
            return name;
        }
    
        public static string GetPropertyName<T>(Expression<Func<T>> exp)
        {
            string rtn = "";
            MemberExpression mex = exp.Body as MemberExpression;
            if(mex!=null)
                rtn = mex.Member.Name;
            return rtn;
        }
    
        public static void SetValue<T>(ref T target, T newVal, object sender, PropertyChangedEventHandler handler, params string[] changed)
        {
            if (!target.Equals(newVal))
            {
                target = newVal;
                PropertyChanged(sender, handler, changed);
            }
        }
        public static void SetValue<T>(ref T target, T newVal, Action<PropertyChangedEventArgs> handler, params string[] changed)
        {
            if (!target.Equals(newVal))
            {
                target = newVal;
                foreach (var item in changed)
                {
                    handler(GetArg(item));
                }
            }
        }
    
        public static void PropertyChanged(object sender,PropertyChangedEventHandler handler,params string[] changed)
        {
            if (handler!=null)
            {
                foreach (var prop in changed)
                {
                    handler(sender, GetArg(prop));
                }
            }
        }
        public static PropertyChangedEventArgs GetArg(string name)
        {
            if (!argslookup.ContainsKey(name)) argslookup.Add(name, new PropertyChangedEventArgs(name));
            return argslookup[name];
        }
    }
    

    编辑:有人建议我从一个助手类转移到一个值包装器,我已经使用这个,我发现它工作得很好

    public class NotifyValue<T>
    {
        public static implicit operator T(NotifyValue<T> item)
        {
            return item.Value;
        }
    
        public NotifyValue(object parent, T value = default(T), PropertyChangingEventHandler changing = null, PropertyChangedEventHandler changed = null, params object[] dependenies)
        {
            _parent = parent;
            _propertyChanged = changed;
            _propertyChanging = changing;
    
            if (_propertyChanged != null)
            {
                _propertyChangedArg =
                    dependenies.OfType<PropertyChangedEventArgs>()
                    .Union(
                        from d in dependenies.OfType<string>()
                        select new PropertyChangedEventArgs(d)
                    );
    
            }
            if (_propertyChanging != null)
            {
                _propertyChangingArg =
                    dependenies.OfType<PropertyChangingEventArgs>()
                    .Union(
                        from d in dependenies.OfType<string>()
                        select new PropertyChangingEventArgs(d)
                    );
            }
            _PostChangeActions = dependenies.OfType<Action>();
    
        }
    
        private T _Value;
    
        public T Value
        {
            get { return _Value; }
            set
            {
                SetValue(value);
            }
        }
    
        public bool SetValue(T value)
        {
            if (!EqualityComparer<T>.Default.Equals(_Value, value))
            {
                OnPropertyChnaging();
                _Value = value;
                OnPropertyChnaged();
                foreach (var action in _PostChangeActions)
                {
                    action();
                }
                return true;
            }
            else
                return false;
        }
    
        private void OnPropertyChnaged()
        {
            var handler = _propertyChanged;
            if (handler != null)
            {
                foreach (var arg in _propertyChangedArg)
                {
                    handler(_parent, arg);
                }           
            }
        }
    
        private void OnPropertyChnaging()
        {
            var handler = _propertyChanging;
            if(handler!=null)
            {
                foreach (var arg in _propertyChangingArg)
                {
                    handler(_parent, arg);
                }
            }
        }
    
        private object _parent;
        private PropertyChangedEventHandler _propertyChanged;
        private PropertyChangingEventHandler _propertyChanging;
        private IEnumerable<PropertyChangedEventArgs> _propertyChangedArg;
        private IEnumerable<PropertyChangingEventArgs> _propertyChangingArg;
        private IEnumerable<Action> _PostChangeActions;
    }
    

    使用的例子

    private NotifyValue<int> _val;
    public const string ValueProperty = "Value";
    public int Value
    {
        get { return _val.Value; }
        set { _val.Value = value; }
    }
    

    然后在构造函数中你做

    _val = new NotifyValue<int>(this,0,PropertyChanged,PropertyChanging,ValueProperty );
    
  • 40

    只需在自动属性声明上方使用此 attribute 即可

    [NotifyParentProperty(true)]
    public object YourProperty { get; set; }
    

相关问题