如何在不使用字符串名称的情况下引发PropertyChanged事件

Bud*_*dda 41 .net reflection lambda binding inotifypropertychanged

能够在没有明确指定已更改属性的名称的情况下引发"PropertyChanged"事件是一件好事.我想做这样的事情:

    public string MyString
    {
        get { return _myString; }
        set
        {
            ChangePropertyAndNotify<string>(val=>_myString=val, value);
        }
    }

    private void ChangePropertyAndNotify<T>(Action<T> setter, T value)
    {
        setter(value);
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(setter.Method.Name));
        }
    }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,收到的名称是lambda方法的名称:"<set_MyString> b__0".

  1. 我可以确定,修剪"<set_"和"> b__0"将始终提供正确的属性名称吗?
  2. 还有其他任何关于财产变更的通知(来自财产本身)?

谢谢.

TCC*_*TCC 39

添加了C#6答案

在C#6(以及Visual Studio 2015附带的VB版本)中,我们拥有了nameof比以往更简单的操作符.在我下面的原始答案中,我使用C#5功能(调用者信息属性)来处理"自我更改"通知的常见情况.该nameof操作员可在所有情况下使用,并在"关联属性更改"通知情景特别有用.

为简单起见,我认为我将保留调用者信息属性方法以用于常见的自我更改通知.更少的输入意味着更少的拼写错误和复制/粘贴诱导错误的机会...这里的编译器确保您选择一个有效的类型/成员/变量,但它不能确保您选择正确的类型/成员/变量.然后使用new nameof运算符进行相关属性更改通知很简单.下面的示例演示了调用者信息属性的关键行为...如果参数由调用者指定,则该属性对参数没有影响(即,仅当参数值被省略时才为参数值提供调用者信息呼叫者,召集者).

值得注意的是nameof,PropertyChanged事件处理程序也可以使用该运算符.现在,您可以使用运算符PropertyName将事件中的值(即a string)与特定属性进行比较nameof,从而消除更多魔术字符串.

参考信息nameof:https://msdn.microsoft.com/en-us/library/dn986596.aspx

例:

public class Program
{
    void Main()
    {
        var dm = new DataModel();
        dm.PropertyChanged += propertyChangedHandler;
    }

    void propertyChangedHandler(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == nameof(DataModel.NumberSquared))
        {
            //do something spectacular
        }
    }
}


public class DataModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

public class DataModel : DataModelBase
{
    //a simple property
    string _something;
    public string Something 
    { 
        get { return _something; } 
        set { _something = value; OnPropertyChanged(); } 
    }

    //a property with another related property
    int _number;
    public int Number
    {
        get { return _number; }

        set 
        { 
            _number = value; 
            OnPropertyChanged(); 
            OnPropertyChanged(nameof(this.NumberSquared)); 
         }
    }

    //a related property
    public int NumberSquared { get { return Number * Number; } }
}
Run Code Online (Sandbox Code Playgroud)

原来的C#5答案

从C#5开始,最好使用调用者信息属性,这在编译时解决,无需反思.

我在基类中实现它,派生类只是OnPropertyChanged从属性设置器中调用方法.如果某个属性隐式更改了另一个值,我也可以在属性设置器中使用该方法的"显式"版本,然后不再"安全",但这是我接受的罕见情况.

或者,您可以将此方法用于自我更改通知,并使用@Jehof给出的相关属性更改通知的答案...这将具有无魔术字符串的优势,对于自我更改通知的常见情况执行速度最快.

这个最新的建议在下面实现(我想我会开始使用它!)

public class DataModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        OnPropertyChangedExplicit(propertyName);
    }

    protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
    {
        var memberExpression = (MemberExpression)projection.Body;
        OnPropertyChangedExplicit(memberExpression.Member.Name);
    }

    void OnPropertyChangedExplicit(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

public class DataModel : DataModelBase
{
    //a simple property
    string _something;
    public string Something 
    { 
        get { return _something; } 
        set { _something = value; OnPropertyChanged(); } 
    }

    //a property with another related property
    int _number;
    public int Number
    {
        get { return _number; }

        set 
        { 
            _number = value; 
            OnPropertyChanged(); 
            OnPropertyChanged(() => NumberSquared); 
         }
    }

    //a related property
    public int NumberSquared { get { return Number * Number; } }
}
Run Code Online (Sandbox Code Playgroud)


Fra*_*nov 29

更新:原始代码不是Windows Phone友好的,因为它依赖于LambdaExpression.Compile()来获取事件源对象.这是更新的扩展方法(也删除了参数检查):

    public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression)
    {
        if (handler != null)
        {
            var body = propertyExpression.Body as MemberExpression;
            var expression = body.Expression as ConstantExpression;
            handler(expression.Value, new PropertyChangedEventArgs(body.Member.Name));
        }
    }
Run Code Online (Sandbox Code Playgroud)

用法保持如下.


您可以使用反射调用属性getter的lambda函数获取属性名称.请注意,您实际上不必调用该lambda,只需要它用于反射:

public static class INotifyPropertyChangedHelper
{
    public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression)
    {
        if (handler != null)
        {
            var body = propertyExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("'propertyExpression' should be a member expression");

            var expression = body.Expression as ConstantExpression;
            if (expression == null)
                throw new ArgumentException("'propertyExpression' body should be a constant expression");

            object target = Expression.Lambda(expression).Compile().DynamicInvoke();

            var e = new PropertyChangedEventArgs(body.Member.Name);
            handler(target, e);
        }
    }

    public static void Raise<T>(this PropertyChangedEventHandler handler, params Expression<Func<T>>[] propertyExpressions)
    {
        foreach (var propertyExpression in propertyExpressions)
        {
            handler.Raise<T>(propertyExpression);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是如何在类中使用该帮助程序来为一个或多个属性引发事件:

PropertyChanged.Raise(() => this.Now);
PropertyChanged.Raise(() => this.Age, () => this.Weight);
Run Code Online (Sandbox Code Playgroud)

请注意,这也帮助在情况下,无操作的PropertyChangednull.


Tim*_*phy 6

在下面的示例中,您必须传递3个值(支持字段,新值,属性为lambda),但没有魔术字符串,属性更改事件仅在真正不相等时才会引发.

class Sample : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set { this.SetProperty(ref _name, value, () => this.Name); }
    }


    protected void SetProperty<T>(ref T backingField, T newValue, Expression<Func<T>> propertyExpression)
    {
        if (backingField == null && newValue == null)
        {
            return;
        }

        if (backingField == null || !backingField.Equals(newValue))
        {
            backingField = newValue;
            this.OnPropertyChanged(propertyExpression);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyExpression.GetPropertyName()));
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

以下代码包含从lambda表达式获取属性名称的扩展方法.

public static class Extensions
{
    public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> propertyExpression)
    {
        return propertyExpression.Body.GetMemberExpression().GetPropertyName();
    }

    public static string GetPropertyName(this MemberExpression memberExpression)
    {
        if (memberExpression == null)
        {
            return null;
        }

        if (memberExpression.Member.MemberType != MemberTypes.Property)
        {
            return null;
        }

        var child = memberExpression.Member.Name;
        var parent = GetPropertyName(memberExpression.Expression.GetMemberExpression());

        if (parent == null)
        {
            return child;
        }
        else
        {
            return parent + "." + child;
        }
    }

    public static MemberExpression GetMemberExpression(this Expression expression)
    {
        var memberExpression = expression as MemberExpression;

        if (memberExpression != null)
        {
            return memberExpression;
        }

        var unaryExpression = expression as UnaryExpression;


        if (unaryExpression != null)
        {
            memberExpression = (MemberExpression)unaryExpression.Operand;

            if (memberExpression != null)
            {
                return memberExpression;
            }

        }
        return null;
    }

    public static void ShouldEqual<T>(this T actual, T expected, string name)
    {
        if (!Object.Equals(actual, expected))
        {
            throw new Exception(String.Format("{0}: Expected <{1}> Actual <{2}>.", name, expected, actual));
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

最后一些测试代码:

class q3191536
{
    public static void Test()
    {
        var sample = new Sample();
        var propertyChanged = 0;

        sample.PropertyChanged += 
            new PropertyChangedEventHandler((sender, e) => 
                {
                    if (e.PropertyName == "Name")
                    {
                        propertyChanged += 1;
                    }
                }
            );

        sample.Name = "Budda";

        sample.Name.ShouldEqual("Budda", "sample.Name");
        propertyChanged.ShouldEqual(1, "propertyChanged");

        sample.Name = "Tim";
        sample.Name.ShouldEqual("Tim", sample.Name);
        propertyChanged.ShouldEqual(2, "propertyChanged");

        sample.Name = "Tim";
        sample.Name.ShouldEqual("Tim", sample.Name);
        propertyChanged.ShouldEqual(2, "propertyChanged");
    }
}
Run Code Online (Sandbox Code Playgroud)


Jeh*_*hof 5

我正在使用扩展方法

public static class ExpressionExtensions {
    public static string PropertyName<TProperty>(this Expression<Func<TProperty>> projection) {
        var memberExpression = (MemberExpression)projection.Body;

        return memberExpression.Member.Name;
    }
}
Run Code Online (Sandbox Code Playgroud)

结合以下方法。该方法在实现INotifyPropertyChanged接口的类中定义(通常是派生我的其他类的基类)。

protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) {
    var e = new PropertyChangedEventArgs(projection.PropertyName());

    OnPropertyChanged(e);
}
Run Code Online (Sandbox Code Playgroud)

然后我可以按如下方式引发PropertyChanged-Event

private double _rate;
public double Rate {
        get {
            return _rate;
        }
        set {
            if (_rate != value) {
              _rate = value;                     
              OnPropertyChanged(() => Rate );
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

使用此方法,可以轻松地重命名Properties(在Visual Studio中),从而确保也更新了相应的PropertyChanged调用。