优化性能:数据绑定

Windows Presentation Foundation (WPF) 数据绑定功能为应用程序提供了一种简单一致的数据呈现与交互方式。 元素可以绑定到各种数据源中的数据,其形式为 CLR 对象和 XML。

本主题提供数据绑定性能方面的建议。

如何解析数据绑定引用

在讨论数据绑定性能问题前,有必要了解 Windows Presentation Foundation (WPF) 数据绑定引擎如何解析用于绑定的对象引用。

Windows Presentation Foundation (WPF) 数据绑定的源可以是任何 CLR 对象。 可绑定到 CLR 对象的属性、子属性或索引器。 绑定引用是通过使用 Microsoft .NET Framework 反射或 ICustomTypeDescriptor 解析的。 以下为解析用于绑定的对象引用的三种方法。

第一种方法涉及到使用反射。 在这种情况下,会使用 PropertyInfo 对象来发现属性的特性并提供对属性元数据的访问。 使用 ICustomTypeDescriptor 接口时,数据绑定引擎使用此接口来访问属性值。 在对象没有一组静态属性的情况下,ICustomTypeDescriptor 接口尤为有用。

可以通过实现 INotifyPropertyChanged 接口或使用与 TypeDescriptor 关联的更改通知来提供属性更改通知。 但是,实现属性更改通知的首选策略是使用 INotifyPropertyChanged

如果源对象是 CLR 对象,并且源属性是 CLR 属性,则 Windows Presentation Foundation (WPF) 数据绑定引擎必须首先使用源对象的反射来获取 TypeDescriptor,然后查询 PropertyDescriptor。 从性能角度看,此反射操作序列可能非常耗时。

解析对象引用的第二种方法涉及实现 INotifyPropertyChanged 接口的 CLR 源对象,以及一种作为 CLR 属性的源属性。 这种情况下,数据绑定引擎直接在源类型上使用反射,并获取所需的属性。 这仍然不是最佳方法,但它在工作集要求方面的成本低于第一种方法。

第三种解析对象引用的方法涉及到作为 DependencyObject 的源对象以及作为 DependencyProperty 的源属性。 这种情况下,数据绑定引擎无需使用反射。 属性引擎和数据绑定引擎共同独立地解析属性引用。 这是解析用于数据绑定的对象引用的最佳方法。

下表比较了通过这三种方法,对一千个Text元素的TextBlock属性进行数据绑定的速度。

绑定 TextBlock 的 Text 属性 绑定时间 (ms) 呈现时间 -- 包括绑定 (ms)
绑定到 CLR 对象的属性 115 314
绑定到实现 INotifyPropertyChanged 的 CLR 对象的属性 115 305
绑定到 DependencyPropertyDependencyObject 90 263

绑定到大型 CLR 对象

将数据绑定到具有数千个属性的单个 CLR 对象时,性能有显著影响。 通过将单个对象划分为多个属性较少的 CLR 对象,可以最大程度地减少这种影响。 下表显示将数据绑定到单个大型 CLR 对象和多个较小对象的绑定时间和呈现时间。

数据绑定 1000 个 TextBlock 对象 绑定时间 (ms) 呈现时间 -- 包括绑定 (ms)
指向具有 1000 个属性的 CLR 对象 950 1200
绑定到 1000 个具有 1 个属性的 CLR 对象 115 314

绑定到 ItemsSource

假设你有一个 CLRList<T> 对象,该对象包含要在 ListBox中显示的员工列表。 若要在这两个对象之间创建对应关系,应将员工列表绑定到 ListBoxItemsSource 属性。 不妨再假设有一个加入组的新员工。 你可能会想,若要将这名新员工插入到绑定的 ListBox 值中,仅需将这名新员工添加到员工列表即可,数据绑定引擎应该会自动识别此更改。 这种看法并不正确;实际上,更改不会自动反映在 ListBox 中。 这是因为 CLRList<T> 对象不会自动引发集合更改事件。 若要使 ListBox 接受更改,你需重新创建员工列表并将它重新附加到 ItemsSourceListBox 属性。 此解决方案尽管起作用,但是会随之产生很大的性能影响。 每次将 ItemsSourceListBox 重新分配给新对象时,ListBox 会首先丢弃其先前的项并重新生成整个列表。 如果 ListBox 映射到复杂的 DataTemplate,则性能影响会被放大。

针对此问题的一个非常高效的解决方案是使员工列表成为 ObservableCollection<T>ObservableCollection<T> 对象会引发一个更改通知,数据绑定引擎可以接收。 此事件从 ItemsControl 添加或删除项,无需重新生成整个列表。

下表显示了添加一个新项时更新 ListBox(其中 UI 虚拟化处于关闭状态)所需的时间。 第一行中的数字表示 CLRList<T> 对象绑定到 ListBox 元素的ItemsSource时经过的时间。 第二行的数字表示当 ObservableCollection<T> 绑定到 ListBox 元素的 ItemsSource时经过的时间。 请注意,使用 ObservableCollection<T> 数据绑定策略能显著节省时间。

数据绑定 ItemsSource 1 个项的更新时间 (ms)
绑定到 CLRList<T> 对象 1656
绑定到 ObservableCollection<T> 20

将 IList 绑定到 ItemsControl 而非 IEnumerable

如果可以在将 IList<T> 还是 IEnumerable 绑定到 ItemsControl 对象之间进行选择,请选择 IList<T> 对象。 将 IEnumerable 绑定到 ItemsControl 会强制 WPF 创建一个包装器 IList<T> 对象,这意味着性能会因第二个对象不必要的开销而受到影响。

不要将 CLR 对象转换为仅用于数据绑定的 XML。

WPF 允许将数据绑定到 XML 内容;但是,数据绑定到 XML 内容的速度比将数据绑定到 CLR 对象的速度慢。 如果唯一的用途是数据绑定,请不要将 CLR 对象数据转换为 XML。

请参阅