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

时间:2020-03-06 14:48:07  来源:igfitidea点击:

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

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

解决方案

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

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

是的,我看到了我们所建议的功能的使用和简单性,但是考虑到由于反射而产生的运行成本,是个坏主意,我在此方案中使用的是适当添加了代码段以利用时间并且在所有Notifyproperty事件触发时写入属性时出错。

此外,我们发现了一个问题,即在Debug版本与Release版本中,获取方法名称的方式有所不同:

http://social.msdn.microsoft.com/Forums/zh-CN/wpf/thread/244d3f24-4cc4-4925-aebe-85f55b39ec92

(我们使用的代码并不能完全按照建议进行反映,但是它使我们确信,对属性名称进行硬编码是最快,最可靠的解决方案。)

我曾经做过一次这样的实验,它从内存中可以正常工作,并且不需要对字符串中的所有属性名称进行硬编码。如果在台式机上构建大量服务器应用程序,则性能可能会出现问题,我们可能永远不会注意到它们之间的差异。

protected void OnPropertyChanged()
{
    OnPropertyChanged(PropertyName);
}

protected string PropertyName
{
    get
    {
        MethodBase mb = new StackFrame(1).GetMethod();
        string name = mb.Name;
        if(mb.Name.IndexOf("get_") > -1)
            name = mb.Name.Replace("get_", "");

        if(mb.Name.IndexOf("set_") > -1)
            name = mb.Name.Replace("set_", "");

        return name;
    }
}

基于反射的方法的问题在于它相当昂贵,而且速度也不是很快。当然,它具有更大的灵活性,并且在重构时不那么脆弱。

但是,它确实会损害性能,尤其是在频繁调用时。我也相信stackframe方法在CAS中存在问题(例如,受限制的信任级别,例如XBAP)。最好对其进行硬编码。

如果我们在WPF中寻找快速,灵活的属性通知,那么有一个解决方案-use DependencyObject :)那就是它的设计目的。如果我们不想获取依赖关系,或者担心线程相似性问题,请将属性名称移到常量中,然后开始吧!你的好。

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

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

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

因此,当然,在触发PropertyChanged时使用硬代码属性名称比使用反射来动态获取属性名称更有效,但是恕我直言,平衡思想很重要。在某些情况下,性能开销不是那么关键,我们可以从某种类型的强类型事件触发机制中受益。

这是我有时在C3.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;
}

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

FirePropertyChanged(p => p.Name);

罗马文:

我想说我们甚至不需要相应的" 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));
  }
}

...但是,我更喜欢在调试版本中坚持使用条件验证的字符串参数。乔什·史密斯(Josh Smith)在此发布了一个不错的示例:

实现INotifyPropertyChanged的基类

干杯:)
菲利普

我们可能要完全避免使用INotifyPropertyChanged。它将不必要的簿记代码添加到项目中。考虑改用Update Controls .NET。

我们可能对此讨论感兴趣

"最佳实践:如何正确实现INotifyPropertyChanged?"

也。

我能想到的另一种非常好的方法是

自动实现INotifyPropertyChanges with Aspects
AOP:面向方面的编程

关于codeproject的不错的文章:InotifyPropertyChanged的AOP实现

表达式树使用中涉及的性能下降是由于表达式树的重复解析所致。

以下代码仍使用表达式树,因此具有重构友好和混淆友好的优点,但实际上比通常的技术快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);
        }
    }
}

看一下这篇博客文章:
http://khason.net/dev/inotifypropertychanged-auto-wiring-or-how-to-get-rid-of-redundant-code