INotifyPropertyChanged属性名称 - 硬编码与反射?

Ker*_*ins 41 .net reflection wpf

使用INotifyPropertyChanged时指定属性名称的最佳方法是什么?

大多数示例将属性名称硬编码为PropertyChanged事件的参数.我在考虑使用MethodBase.GetCurrentMethod.Name.Substring(4),但对反射开销有点不安.

Rom*_*ier 45

不要忘记一件事:PropertyChanged事件主要由将使用反射来获取命名属性值的组件使用.

最明显的例子是数据绑定.

当您触发PropertyChanged事件时,将属性的名称作为参数传递,您应该知道此事件的订阅者可能通过调用(例如,GetProperty至少第一次使用缓存PropertyInfo)来使用反射,然后GetValue.最后一次调用是属性getter方法的动态调用(MethodInfo.Invoke),其成本高于GetProperty仅查询元数据的成本.(请注意,数据绑定依赖于整个TypeDescriptor事物 - 但默认实现使用反射.)

因此,当然,在触发PropertyChanged时使用硬代码属性名称比使用反射动态获取属性名称更有效,但恕我直言,重要的是平衡你的想法.在某些情况下,性能开销并不重要,您可以从强类型事件触发机制中获益.

这是我有时在C#3.0中使用的,当时表演不会引起关注:

public class Person : INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return this.name; }
        set 
        { 
            this.name = value;
            FirePropertyChanged(p => p.Name);
        }
    }

    private void FirePropertyChanged<TValue>(Expression<Func<Person, TValue>> propertySelector)
    {
        if (PropertyChanged == null)
            return;

        var memberExpression = propertySelector.Body as MemberExpression;
        if (memberExpression == null)
            return;

        PropertyChanged(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
Run Code Online (Sandbox Code Playgroud)

注意使用表达式树来获取属性的名称,并使用lambda表达式作为Expression:

FirePropertyChanged(p => p.Name);
Run Code Online (Sandbox Code Playgroud)

  • 对于财产变更问题,这似乎有些过分.虽然它以可重构代码维护所有内容,但最终需要维护更多代码,并且代码的可读性低于OnPropertyChanged("Name")或类似方法的原始概念. (2认同)
  • 很酷.你认为有没有办法完全避免使用字符串? (2认同)
  • 当你消费这个事件我的意思是.而不是使用INotifyPropertyChanged,是否有一个接口可以创建,其中EventArgs在第一个位置没有使用字符串文字? (2认同)
  • 请注意,Microsoft从未显示过反射解决方案的示例.在.NET 4.5中,我们现在可以使用新的[CallerMemberName]属性http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx (2认同)

Den*_*els 22

在.NET 4.5(C#5.0)中有一个名为CallerMemberName的新属性,如果开发人员决定更改属性名称,它有助于避免硬编码属性名称阻止错误的发生,这是一个示例:

public event PropertyChangedEventHandler PropertyChanged = delegate { };

public void OnPropertyChanged([CallerMemberName]string propertyName="")
{
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

private string name;
public string Name
{
    get { return name; }
    set 
    { 
        name = value;
        OnPropertyChanged();
    }
}
Run Code Online (Sandbox Code Playgroud)


Ori*_*ian 19

这里的反射开销实在是太过分了,特别是因为INotifyPropertyChanged被调用很多.如果可以的话,最好只是对值进行硬编码.

如果你不关心性能,那么我会看看下面提到的各种方法,并选择需要最少量编码的方法.如果你可以做一些事情来完全消除对显式调用的需要那么那将是最好的(例如AOP).


Phi*_*hil 14

使用表达式树所涉及的性能损失是由于表达式树的重复解析.

下面的代码仍然使用表达式树,因此具有重构友好和混淆友好的优点,但实际上比通常的技术快40%(非常粗糙的测试) - 包括为每个更改通知新增一个PropertyChangedEventArgs对象.

它更快,并且避免了表达式树的性能损失,因为我们为每个属性缓存一个静态PropertyChangedEventArgs对象.

有一件事我还没做 - 我打算添加一些代码来检查调试版本,提供的PropertChangedEventArgs对象的属性名称与使用它的属性相匹配 - 此时此代码仍然是开发人员可能提供错误的对象.

看看这个:

    public class Observable<T> : INotifyPropertyChanged
    where T : Observable<T>
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected static PropertyChangedEventArgs CreateArgs(
        Expression<Func<T, object>> propertyExpression)
    {
        var lambda = propertyExpression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        var propertyInfo = memberExpression.Member as PropertyInfo;

        return new PropertyChangedEventArgs(propertyInfo.Name);
    }

    protected void NotifyChange(PropertyChangedEventArgs args)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, args);
        }
    }
}

public class Person : Observable<Person>
{
    // property change event arg objects
    static PropertyChangedEventArgs _firstNameChangeArgs = CreateArgs(x => x.FirstName);
    static PropertyChangedEventArgs _lastNameChangeArgs = CreateArgs(x => x.LastName);

    string _firstName;
    string _lastName;

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            NotifyChange(_firstNameChangeArgs);
        }
    }

    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            NotifyChange(_lastNameChangeArgs);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 11

罗马:

我会说你甚至不需要"Person"参数 - 因此,像下面那样的完全通用的代码段应该:

private int age;
public int Age
{
  get { return age; }
  set
  {
    age = value;
    OnPropertyChanged(() => Age);
  }
}


private void OnPropertyChanged<T>(Expression<Func<T>> exp)
{
  //the cast will always succeed
  MemberExpression memberExpression = (MemberExpression) exp.Body;
  string propertyName = memberExpression.Member.Name;

  if (PropertyChanged != null)
  {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
}
Run Code Online (Sandbox Code Playgroud)

...但是,我更喜欢在Debug版本中使用条件验证来坚持字符串参数.Josh Smith发布了一个很好的样本:

实现INotifyPropertyChanged的基类

干杯:)菲利普


Job*_*Joy 6

是的,我看到你所建议的功能的使用和简单,但是当考虑反射的运行成本时,是的,这是一个坏主意,我在这种情况下使用的是正确添加代码片段以利用时间编写一个具有所有Notifyproperty事件触发的属性时出错.