优化性能:数据绑定
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 的源属性。 这种情况下,数据绑定引擎无需使用反射。 属性引擎和数据绑定引擎共同独立地解析属性引用。 这是解析用于数据绑定的对象引用的最佳方法。
下表比较了使用这三种方法对一千个 TextBlock 元素的 Text 属性进行数据绑定的速度。
绑定 TextBlock 的文本属性 | 绑定时间 (ms) | 呈现时间 -- 包括绑定 (ms) |
---|---|---|
绑定到 CLR 对象的属性 | 115 | 314 |
绑定到实现 INotifyPropertyChanged 的 CLR 对象的属性 | 115 | 305 |
绑定到 DependencyObject 的 DependencyProperty。 | 90 | 263 |
绑定到大型 CLR 对象
数据绑定到具有数千个属性的单个 CLR 对象时会出现明显的性能影响。 可以通过将单个对象分成具有较少属性的多个 CLR 对象来最大限度地降低此影响。 下表列出了数据绑定到单个大型 CLR 对象和多个较小对象的绑定时间和呈现时间。
数据绑定 1000 个 TextBlock 对象 | 绑定时间 (ms) | 呈现时间 -- 包括绑定 (ms) |
---|---|---|
绑定到具有 1000 个属性的 CLR 对象 | 950 | 1200 |
绑定到 1000 个具有 1 个属性的 CLR 对象 | 115 | 314 |
绑定到 ItemsSource
假设你有一个包含员工列表的 CLR List<T> 对象,并且你想在 ListBox 中显示此列表。 若要在这两个对象之间创建对应关系,应将员工列表绑定到 ListBox 的 ItemsSource 属性。 不妨再假设有一个加入组的新员工。 你可能会想,若要将这名新员工插入到绑定的 ListBox 值中,仅需将这名新员工添加到员工列表即可,数据绑定引擎应该会自动识别此更改。 这种看法并不正确;实际上,更改不会自动反映在 ListBox 中。 这是因为 CLR List<T> 对象不会自动引发集合更改事件。 若要使 ListBox 接受更改,你需重新创建员工列表并将它重新附加到 ListBox 的 ItemsSource 属性。 此解决方案尽管起作用,但是会随之产生很大的性能影响。 每当将 ListBox 的 ItemsSource 重新分配到新对象时,ListBox 会首先抛弃它先前的项,然后重新生成它的整个列表。 如果 ListBox 映射到复杂的 DataTemplate,则性能影响会被放大。
针对此问题的一个非常高效的解决方案是使员工列表成为 ObservableCollection<T>。 ObservableCollection<T> 对象会引发数据绑定引擎可接收的更改通知。 此事件从 ItemsControl 添加或删除项,无需重新生成整个列表。
下表显示了添加一个新项时更新 ListBox(其中 UI 虚拟化处于关闭状态)所需的时间。 第一行中的数字表示 CLR List<T> 对象绑定到 ListBox 元素的 ItemsSource 的用时。 第二行中的数字表示 ObservableCollection<T> 绑定到 ListBox 元素的 ItemsSource 的用时。 请注意,使用 ObservableCollection<T> 数据绑定策略可节省大量时间。
数据绑定 ItemsSource | 1 个项的更新时间 (ms) |
---|---|
绑定到 CLR List<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 对象的速度更慢。 如果目的仅在于数据绑定,请勿将 CLE 对象数据转换为 XML。