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

时间:2008-09-26 19:15:43

标签: .net wpf reflection

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

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

16 个答案:

答案 0 :(得分: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;
}

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

FirePropertyChanged(p => p.Name);

答案 1 :(得分:20)

在.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();
    }
}

答案 2 :(得分:19)

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

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

答案 3 :(得分: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);
        }
    }
}

答案 4 :(得分: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));
  }
}

...但是,我更喜欢在Debug版本中使用条件验证来坚持字符串参数。约什·史密斯发表了一个很好的样本:

A base class which implements INotifyPropertyChanged

干杯:) 菲利普

答案 5 :(得分:6)

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

答案 6 :(得分:4)

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

使用Aspects自动实现INotifyPropertyChanged
AOP:面向方面的编程

关于codeproject的好文章:AOP Implementation of INotifyPropertyChanged

答案 7 :(得分:3)

答案 8 :(得分:2)

在Hardcode和反射之间没有任何不相干之处,我的选择是:notifypropertyweaver

这个Visual Studio包允许您获得反射(可维护性,可读性,......)的好处,而不必丢失性能。

实际上,你只需要实现INotifyPropertyChanged并在编译中添加所有“通知内容”。

如果您想完全优化代码,这也是完全可参数化的。

例如,使用notifypropertyweaver,您将在编辑器中使用此代码:

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string GivenNames { get; set; }
    public string FamilyName { get; set; }

    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }
}

而不是:

public class Person : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    private string givenNames;
    public string GivenNames
    {
        get { return givenNames; }
        set
        {
            if (value != givenNames)
            {
                givenNames = value;
                OnPropertyChanged("GivenNames");
                OnPropertyChanged("FullName");
            }
        }
    }

    private string familyName;
    public string FamilyName
    {
        get { return familyName; }
        set
        {
            if (value != familyName)
            {
                familyName = value;
                OnPropertyChanged("FamilyName");
                OnPropertyChanged("FullName");
            }
        }
    }

    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }

    public virtual void OnPropertyChanged(string propertyName)
    {
        var propertyChanged = PropertyChanged;
        if (propertyChanged != null)
        {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

对于讲法语的人:Améliorez la lisibilité de votre code et simplifiez vous la vie avec notifypropertyweaver

答案 9 :(得分:1)

此外,我们发现在调试与发布版本中获取方法名称的方式不同的问题:

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

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

答案 10 :(得分:1)

基于反射的方法的问题在于它相当昂贵,并且不是非常快。当然,它更灵活,对重构更不脆弱。

然而,它确实会损害性能,特别是在频繁调用时。堆栈帧方法(我相信)在CAS中也存在问题(例如受限制的信任级别,例如XBAP)。最好硬编码。

如果你在WPF中寻找快速,灵活的属性通知有一个解决方案 - 使用DependencyObject :)那就是它的设计目的。如果您不想接受依赖关系,或担心线程关联性问题,请将属性名称移动到常量,然后繁荣!你的好。

答案 11 :(得分:1)

您可能希望完全避免使用INotifyPropertyChanged。它为您的项目添加了不必要的簿记代码。请考虑使用Update Controls .NET代替。

答案 12 :(得分:1)

答案 13 :(得分:1)

由于C#6.0有一个 nameof()关键字,它将在编译时进行评估,因此它将具有硬编码值的性能,并且可以防止与通知属性不匹配。

public event PropertyChangedEventHandler PropertyChanged;

protected void NotifyPropertyChanged(string info)
{       
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
public string SelectedItem
{
    get
    {
        return _selectedItem;
    }
    set
    {
        if (_selectedItem != value)
        {
            _selectedItem = value;
            NotifyPropertyChanged(nameof(SelectedItem));
        }
    }
}
private string _selectedItem;

答案 14 :(得分:0)

我做过一次这样的实验,从内存开始就运行正常,并且不再需要对字符串中的所有属性名进行硬编码。如果您构建一个高容量的服务器应用程序,性能可能会成为问题,在桌面上您可能永远不会注意到差异。

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

答案 15 :(得分:0)

相关问题