优化 C# 代码片段、ObservableCollection 和 AddRange
Optimizing C# code fragment, ObservableCollection and AddRange
我正在分析其他人编写的 silverlight 组件。
我发现了很多热点和瓶颈,现在我遇到了这个:
public static class CollectionExtensions
{
public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
{
collection.Add(item);
}
}
}
这个扩展方法,当然是在ObservableCollection中添加了AddRange方法,但是计算量还是比较大的。
有没有人有更好的实现,或者对如何提高这段代码的性能有什么建议?
谢谢
这里的成本通常是由于为每个单独的添加引发的更改通知。更好的做法是创建一个新的集合实现,该实现针对接受的数据范围进行了优化。您可以添加所有值,然后引发单个事件,而不是为每个更改引发更改通知,然后绑定引擎将每个更新作为单个更新进行处理。此事件可以有 大锤子 作为 Reset
,或者您可以提供更改的项目,以及它们从哪里更改的索引。
这是一个在其 AddRange
方法上使用单个 Reset
通知的示例:
/// <summary>
/// An implementation of <seealso cref="ObservableCollection{T}"/> that provides the ability to suppress
/// change notifications. In sub-classes that allows performing batch work and raising notifications
/// on completion of work. Standard usage takes advantage of this feature by providing AddRange method.
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
public class ObservableList<T> : ObservableCollection<T>
{
#region Fields
private readonly Queue<PropertyChangedEventArgs> _notifications = new Queue<PropertyChangedEventArgs>();
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionNotifications = new Queue<NotifyCollectionChangedEventArgs>();
private int _notificationSupressionDepth;
#endregion
public ObservableList()
{
}
public ObservableList(IEnumerable<T> collection)
: base(collection)
{
}
public void AddRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
foreach (var item in list)
{
Add(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
foreach (var item in list)
{
Remove(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ReplaceRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
Clear();
foreach (var item in list)
{
Add(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (_notificationSupressionDepth == 0)
{
base.OnCollectionChanged(e);
}
else
{
//We cant filter duplicate Collection change events as this will break how UI controls work. -LC
_collectionNotifications.Enqueue(e);
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (_notificationSupressionDepth == 0)
{
base.OnPropertyChanged(e);
}
else
{
if (!_notifications.Contains(e, NotifyEventComparer.Instance))
{
_notifications.Enqueue(e);
}
}
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected IDisposable QueueNotifications()
{
_notificationSupressionDepth++;
return Disposable.Create(() =>
{
_notificationSupressionDepth--;
TryNotify();
});
}
protected IDisposable SupressNotifications()
{
_notificationSupressionDepth++;
return Disposable.Create(() =>
{
_notificationSupressionDepth--;
});
}
private void TryNotify()
{
if (_notificationSupressionDepth == 0)
{
while (_collectionNotifications.Count > 0)
{
var collectionNotification = _collectionNotifications.Dequeue();
base.OnCollectionChanged(collectionNotification);
}
while (_notifications.Count > 0)
{
var notification = _notifications.Dequeue();
base.OnPropertyChanged(notification);
}
}
}
}
编辑:添加缺失的 NotifyEventComparer
class 和示例 Disposable.Create
方法
public sealed class NotifyEventComparer : IEqualityComparer<PropertyChangedEventArgs>
{
public static readonly NotifyEventComparer Instance = new NotifyEventComparer();
bool IEqualityComparer<PropertyChangedEventArgs>.Equals(PropertyChangedEventArgs x, PropertyChangedEventArgs y)
{
return x.PropertyName == y.PropertyName;
}
int IEqualityComparer<PropertyChangedEventArgs>.GetHashCode(PropertyChangedEventArgs obj)
{
return obj.PropertyName.GetHashCode();
}
}
//Either use Rx to access Disposable.Create or this simple implementation will do
public static class Disposable
{
public static IDisposable Create(Action dispose)
{
if (dispose == null)
throw new ArgumentNullException("dispose");
return new AnonymousDisposable(dispose);
}
private sealed class AnonymousDisposable : IDisposable
{
private Action _dispose;
public AnonymousDisposable(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
var dispose = Interlocked.Exchange(ref _dispose, null);
if (dispose != null)
{
dispose();
}
}
}
}
您可以在此处查看 AddRange 方法的实现(对于 List):
http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs
多次调用 Add
会导致 INotifyCollectionChanged
被多次引发,通常会导致 UI 重绘自身。
虽然 Lee 的回答在技术上是正确的,即在添加所有项目后引发 Reset
事件是正确的方法,但我从经验中发现许多网格控件(例如)并不主动支持 Reset
事件。
最普遍支持的选项是修改集合远离 ObservableCollection
并重新创建 ObservableCollection
属性 本身。
换句话说,您的 ObservableCollection
在您的 VM 上定义如下...
private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items {
get { return _items;}
set
{
_items = value;
OnPropertyChanged(()=> Items);
}
}
...添加您的新项目如下...
var tempColl = _items.ToList();
tempColl.AddRange(newItems);
Items = new ObservableCollection(tempColl);
关于此技术要记住的另一件事是它是线程安全的,因为如果您重新创建 ObservableCollection
,您可以从后台线程向 ObservableCollection
添加项目。普通 ObservableCollection
无法通过非 Dispatcher 线程的 Add
方法添加项目。
这是由于 ObservableCollection 每次将项目添加到集合时都会触发 PropertyChanged 事件。在批量添加项目时防止触发此事件是您想要查看的内容。这是一个优雅的解决方案,虽然我自己还没有尝试过。
https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/
此线程中已经有一个可接受的答案,但对于所有正在寻找 ObservableRangeCollection 支持 AddRange 和的良好实现的人ReplaceRange 与单个 CollectionChanged 通知我真的会推荐 this piece of code written by James Montemagno。
我正在分析其他人编写的 silverlight 组件。 我发现了很多热点和瓶颈,现在我遇到了这个:
public static class CollectionExtensions
{
public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
{
collection.Add(item);
}
}
}
这个扩展方法,当然是在ObservableCollection中添加了AddRange方法,但是计算量还是比较大的。 有没有人有更好的实现,或者对如何提高这段代码的性能有什么建议?
谢谢
这里的成本通常是由于为每个单独的添加引发的更改通知。更好的做法是创建一个新的集合实现,该实现针对接受的数据范围进行了优化。您可以添加所有值,然后引发单个事件,而不是为每个更改引发更改通知,然后绑定引擎将每个更新作为单个更新进行处理。此事件可以有 大锤子 作为 Reset
,或者您可以提供更改的项目,以及它们从哪里更改的索引。
这是一个在其 AddRange
方法上使用单个 Reset
通知的示例:
/// <summary>
/// An implementation of <seealso cref="ObservableCollection{T}"/> that provides the ability to suppress
/// change notifications. In sub-classes that allows performing batch work and raising notifications
/// on completion of work. Standard usage takes advantage of this feature by providing AddRange method.
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
public class ObservableList<T> : ObservableCollection<T>
{
#region Fields
private readonly Queue<PropertyChangedEventArgs> _notifications = new Queue<PropertyChangedEventArgs>();
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionNotifications = new Queue<NotifyCollectionChangedEventArgs>();
private int _notificationSupressionDepth;
#endregion
public ObservableList()
{
}
public ObservableList(IEnumerable<T> collection)
: base(collection)
{
}
public void AddRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
foreach (var item in list)
{
Add(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
foreach (var item in list)
{
Remove(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ReplaceRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
Clear();
foreach (var item in list)
{
Add(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (_notificationSupressionDepth == 0)
{
base.OnCollectionChanged(e);
}
else
{
//We cant filter duplicate Collection change events as this will break how UI controls work. -LC
_collectionNotifications.Enqueue(e);
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (_notificationSupressionDepth == 0)
{
base.OnPropertyChanged(e);
}
else
{
if (!_notifications.Contains(e, NotifyEventComparer.Instance))
{
_notifications.Enqueue(e);
}
}
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected IDisposable QueueNotifications()
{
_notificationSupressionDepth++;
return Disposable.Create(() =>
{
_notificationSupressionDepth--;
TryNotify();
});
}
protected IDisposable SupressNotifications()
{
_notificationSupressionDepth++;
return Disposable.Create(() =>
{
_notificationSupressionDepth--;
});
}
private void TryNotify()
{
if (_notificationSupressionDepth == 0)
{
while (_collectionNotifications.Count > 0)
{
var collectionNotification = _collectionNotifications.Dequeue();
base.OnCollectionChanged(collectionNotification);
}
while (_notifications.Count > 0)
{
var notification = _notifications.Dequeue();
base.OnPropertyChanged(notification);
}
}
}
}
编辑:添加缺失的 NotifyEventComparer
class 和示例 Disposable.Create
方法
public sealed class NotifyEventComparer : IEqualityComparer<PropertyChangedEventArgs>
{
public static readonly NotifyEventComparer Instance = new NotifyEventComparer();
bool IEqualityComparer<PropertyChangedEventArgs>.Equals(PropertyChangedEventArgs x, PropertyChangedEventArgs y)
{
return x.PropertyName == y.PropertyName;
}
int IEqualityComparer<PropertyChangedEventArgs>.GetHashCode(PropertyChangedEventArgs obj)
{
return obj.PropertyName.GetHashCode();
}
}
//Either use Rx to access Disposable.Create or this simple implementation will do
public static class Disposable
{
public static IDisposable Create(Action dispose)
{
if (dispose == null)
throw new ArgumentNullException("dispose");
return new AnonymousDisposable(dispose);
}
private sealed class AnonymousDisposable : IDisposable
{
private Action _dispose;
public AnonymousDisposable(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
var dispose = Interlocked.Exchange(ref _dispose, null);
if (dispose != null)
{
dispose();
}
}
}
}
您可以在此处查看 AddRange 方法的实现(对于 List): http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs
多次调用 Add
会导致 INotifyCollectionChanged
被多次引发,通常会导致 UI 重绘自身。
虽然 Lee 的回答在技术上是正确的,即在添加所有项目后引发 Reset
事件是正确的方法,但我从经验中发现许多网格控件(例如)并不主动支持 Reset
事件。
最普遍支持的选项是修改集合远离 ObservableCollection
并重新创建 ObservableCollection
属性 本身。
换句话说,您的 ObservableCollection
在您的 VM 上定义如下...
private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items {
get { return _items;}
set
{
_items = value;
OnPropertyChanged(()=> Items);
}
}
...添加您的新项目如下...
var tempColl = _items.ToList();
tempColl.AddRange(newItems);
Items = new ObservableCollection(tempColl);
关于此技术要记住的另一件事是它是线程安全的,因为如果您重新创建 ObservableCollection
,您可以从后台线程向 ObservableCollection
添加项目。普通 ObservableCollection
无法通过非 Dispatcher 线程的 Add
方法添加项目。
这是由于 ObservableCollection 每次将项目添加到集合时都会触发 PropertyChanged 事件。在批量添加项目时防止触发此事件是您想要查看的内容。这是一个优雅的解决方案,虽然我自己还没有尝试过。
https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/
此线程中已经有一个可接受的答案,但对于所有正在寻找 ObservableRangeCollection 支持 AddRange 和的良好实现的人ReplaceRange 与单个 CollectionChanged 通知我真的会推荐 this piece of code written by James Montemagno。