微软应该为INotifyPropertyChanged实现一些时髦的东西,就像在自动属性中,只需要指定{get;设置;通知;} 我认为这样做很有意义。或者做这个手术有什么并发症吗?

我们能在属性中实现类似notify的东西吗。在你的类中实现INotifyPropertyChanged是否有一个优雅的解决方案,或者唯一的方法是在每个属性中引发PropertyChanged事件。

如果不是,我们可以写一些东西来自动生成一段代码来引发PropertyChanged事件?


当前回答

我实际上还没有机会尝试这个自己,但下次我设置一个大需求的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使用一个自定义模板,但即使这样,我也厌倦了我所有的属性太长。


啊,一个快速的谷歌搜索(我应该在写这篇文章之前做的)显示至少有一个人在这里之前做过类似的事情。跟我想的不完全一样,但也足以证明这个理论是正确的。

其他回答

让我介绍一下我自己的方法,叫做Yappi。 它属于运行时代理|派生类生成器,向现有对象或类型添加新功能,如种姓项目的动态代理。

它允许在基类中实现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);
            };
        }
    }
}

它对于重构是完全安全的,在类型构造后不使用反射,并且足够快。

我建议使用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>();
}

(文档)

我实际上还没有机会尝试这个自己,但下次我设置一个大需求的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使用一个自定义模板,但即使这样,我也厌倦了我所有的属性太长。


啊,一个快速的谷歌搜索(我应该在写这篇文章之前做的)显示至少有一个人在这里之前做过类似的事情。跟我想的不完全一样,但也足以证明这个理论是正确的。

我意识到这个问题已经有无数个答案了,但没有一个适合我。我的问题是我不想有任何性能上的影响,并愿意忍受一点冗长的原因。我也不太关心自动属性,这让我想到了以下解决方案:

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

对于每一个单独的属性,这将成为一个额外的冗长的噩梦;-(

注意,我并不是说这个解决方案在性能上比其他解决方案更好,只是说对于那些不喜欢其他解决方案的人来说,它是一个可行的解决方案。

我在我的博客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); }
    }
}