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

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

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


当前回答

让我介绍一下我自己的方法,叫做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);
            };
        }
    }
}

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

其他回答

反射:利用反射的思想:

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

它是2022。现在有了官方的解决方案。

使用Microsoft MVVM工具包中的MVVM源生成器。

This

[ObservableProperty]
private string? name;

将生成:

private string? name;

public string? Name
{
    get => name;
    set
    {
        if (!EqualityComparer<string?>.Default.Equals(name, value))
        {
            OnNameChanging(value);
            OnPropertyChanging();
            name = value;
            OnNameChanged(value);
            OnPropertyChanged();
        }
    }
}

// Property changing / changed listener
partial void OnNameChanging(string? value);
partial void OnNameChanged(string? value);

protected void OnPropertyChanging([CallerMemberName] string? propertyName = null)
{
    PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}

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

它支持。net标准2.0和。net >= 5.0。

我使用以下扩展方法(使用c# 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); }
}

如果你在。net 4.5中使用动态,你不需要担心INotifyPropertyChanged。

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

如果Name被绑定到某个控件,它就可以正常工作。

还有Fody,它有一个AddINotifyPropertyChangedInterface插件,它让你这样写:

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

...并在编译时注入属性更改通知。