协慌网

登录 贡献 社区

实现 INotifyPropertyChanged - 是否存在更好的方法?

INotifyPropertyChanged实现了一些简单的功能,例如在自动属性中,只需指定{get; set; notify;}我认为这样做很有意义。还是有任何并发症要做?

我们自己可以在属性中实现 “通知” 之类的功能吗?是否有一个优雅的解决方案可在您的类中INotifyPropertyChanged或唯一的方法是在每个属性中PropertyChanged

如果不能,我们是否可以编写一些代码来自动生成引发PropertyChanged事件的代码?

答案

在不使用 postharp 之类的东西的情况下,我使用的最低版本使用了诸如以下内容:

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

那么每个属性都类似于:

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, "Name"); }
}

这不是很大;如果需要,它也可以用作基本类。 SetFieldbool返回值告诉您是否为空操作,以防您要应用其他逻辑。


甚至更容易使用 C#5:

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

可以这样称呼:

set { SetField(ref name, value); }

编译器将使用该名称自动"Name"


C#6.0 使实现更容易:

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

... 现在使用 C#7:

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}

而且,使用 C#8 和 Nullable 引用类型,它看起来像这样:

public event PropertyChangedEventHandler? PropertyChanged;

protected void OnPropertyChanged(string? propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}

从. Net 4.5 开始,终于有了一种简单的方法来执行此操作。

.Net 4.5 引入了新的呼叫者信息属性。

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

最好在函数中添加一个比较器。

EqualityComparer<T>.Default.Equals

这里这里有更多示例

另请参阅呼叫者信息(C#和 Visual Basic)

我真的很喜欢 Marc 的解决方案,但是我认为可以稍微加以改进,以避免使用 “魔术字符串”(不支持重构)。与其使用属性名称作为字符串,不如使其成为 lambda 表达式:

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

只需将以下方法添加到 Marc 的代码中,即可达到目的:

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    if (selectorExpression == null)
        throw new ArgumentNullException("selectorExpression");
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null)
        throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}

protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

顺便说一句,这是受此博客文章更新 URL 的启发