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

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

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


当前回答

从。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)

其他回答

这就是大规模的过度工程。这比仅仅以正确的方式进行要复杂得多,而且几乎没有任何好处。如果你的IDE支持代码片段(Visual Studio/MonoDevelop支持),那么你可以让这个实现变得非常简单。你需要输入的只是属性的类型和属性名。额外的三行代码将自动生成。

我真的很喜欢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;
}

顺便说一句,这是受到这篇博客文章的启发。

这是一个Unity3D或非callermembername版本的NotifyPropertyChanged

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> property)
    {
        var expression = (MemberExpression) property.Body;
        var propertyName = expression.Member.Name;

        Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
        return propertyName;
    }

    #region Notification Handlers

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

这段代码允许你像这样编写属性支持字段:

  public string Text
    {
        get { return Get<string>(); }
        set { Set(value); }
    }

此外,在resharper中,如果你创建了一个模式/搜索片段,你也可以通过将简单的道具字段转换为上面的支持来自动化你的工作流程。

搜索模式:

public $type$ $fname$ { get; set; }

替换模式:

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}

另一个组合解决方案是使用StackFrame:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(ref T field, T value)
    {
        MethodBase method = new StackFrame(1).GetMethod();
        field = value;
        Raise(method.Name.Substring(4));
    }

    protected void Raise(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

用法:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}

在实现这些类型的属性时,你可能要考虑的其他事情是INotifyPropertyChang *ed *ing都使用事件参数类。

如果要设置大量的属性,那么事件参数类实例的数量可能非常大,您应该考虑缓存它们,因为它们是可能发生字符串爆炸的区域之一。

看一下这个实现,并解释为什么要考虑它。

Josh Smiths博客