在.NET框架中,数据绑定是一个常见的需求,而ObservableCollection
是实现数据绑定的常用集合类型。它能够在集合发生变化时,如添加、移除或刷新时,自动通知绑定的UI元素进行更新。然而,在某些情况下,可能需要延迟或暂时禁用这些通知,例如在批量更新数据时,以提高性能或避免界面闪烁。遗憾的是,标准的ObservableCollection
并没有提供这样的功能。
为了解决这个问题,ObservableCollectionEx
应运而生。它旨在提供缺失的功能,即延迟或禁用通知,同时完全兼容ObservableCollection
的使用方式。
为了实现通知的延迟,需要暂时将通知重定向到一个存储位置,并在不再需要延迟时一次性触发它们。同时,还需要为不需要延迟通知的集合消费者提供正常的功能和通知。这可以通过提供多个包装同一集合的外壳来实现。一个外壳实例包含元素容器并托管所有通知事件,而其他外壳实例则处理禁用和延迟的事件。这些额外的外壳引用相同的容器,但它们不会触发变更事件,而是收集这些事件,并在外壳释放时一次性触发它们。
ObservableCollection
的实现基于一个Collection
类,该类实现了ICollection
的操作和ObservableCollection
的实现通知。Collection
类作为IList
接口的外壳实现,包含对容器的引用,并通过它操作容器。Collection
类的一个构造函数接受List
作为参数,允许这个列表成为该Collection
的容器。这为提供了多个Collection
实例来操作同一个容器,这正是所需要的。
不幸的是,这种能力在ObservableCollection
的实现中丢失了。它不是将IList
分配给实例作为容器,而是创建了那个List
的副本,并使用该副本存储元素。这个限制阻止了从ObservableCollection
类继承。
ObservableCollectionEx
基于Collection
类(与ObservableCollection
相同),并实现了与ObservableCollection
完全相同的方法和属性。除了这些成员之外,ObservableCollectionEx
还暴露了两个方法来创建禁用或延迟通知的外壳。
将ObservableCollectionEx
类包含到项目中最简单的方法是通过安装Nuget包。它应该像ObservableCollection
一样使用。它可以实例化并用作ObservableCollection
的替代品,或者从中派生。不需要特殊处理。
为了延迟通知,建议使用using
指令:
ObservableCollectionEx target = new ObservableCollectionEx();
using (ObservableCollectionEx iDelayed = target.DelayNotifications())
{
iDelayed.Add(item0);
iDelayed.Add(item0);
iDelayed.Add(item0);
}
由于通知参数的设计,不可能将不同的操作组合在一起。例如,不可能在同一个延迟实例上同时添加和移除元素,除非在这些调用之间调用了Dispose()
。调用Dispose()
将触发之前收集的事件,并重新初始化操作。
一般来说,ObservableCollection
和ObservableCollectionEx
提供可比较的性能。测试使用了包含10,000个唯一对象的数组。两者都使用这个数组初始化以预分配存储空间,因此不影响计时结果。应用程序运行了大约一打次,以让JIT优化可执行文件,然后收集测试结果。
测试包括10,000次添加、替换和移除操作。使用Stopwatch
类收集计时,并以毫秒为单位呈现。
从图表中可以看出,禁用通知的接口性能与订阅者数量无关。由于一些性能增强,ObservableCollectionEx
无论订阅者数量如何,都比ObservableCollection
稍微表现得更好,但显然一旦有多个订阅者,它就失去了禁用接口。
当通知被延迟时,ObservableCollectionEx
的性能与上述结果不同。由于通知只调用一次,它节省了一些时间,但它需要一些额外的处理来展开保存的通知。ObservableCollection
和ObservableCollectionEx
的通知时间由以下方程式描述:
ObservableCollection:
overhead = (n * a) + (n * b)
ObservableCollectionEx:
overhead = a + c + (n * b)
其中a
是执行通知所需的恒定开销,n
是变更元素的数量,b
是重绘每个单独元素的成本,c
是执行延迟通知所需的开销。
在这些方程式中,a
和c
是常数,因此性能仅取决于两个元素:b
——重绘每个元素所需的时间,和n
——通知的元素数量。如所知,b
控制图表上升的陡度。因此,当重绘每个元素所需的时间(b
)增加时,这两条线更早地相遇。这意味着看到性能优势所需的变更元素数量更少。