优化C#代码片段、ObservableCollection和AddRange

优化C#代码片段、ObservableCollection和AddRange,c#,silverlight,optimization,collections,profiling,C#,Silverlight,Optimization,Collections,Profiling,我正在分析其他人编写的silverlight组件。 我发现了很多热点和瓶颈,现在我遇到了这个: public static class CollectionExtensions { public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items) { foreach (var item in items)

我正在分析其他人编写的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);
        }
    }
}
公共静态类集合扩展
{
公共静态void AddRange(此ObservableCollection集合,IEnumerable项)
{
foreach(项目中的var项目)
{
集合。添加(项目);
}
}
}
当然,这个扩展方法将AddRange方法添加到ObservaleCollection中,但它的计算量相当大。 是否有人有更好的实现,或者对如何提高这段cose的性能有什么建议


谢谢

这里的成本通常是由于为每个单独添加而发出的更改通知。更可取的做法是创建一个新的集合实现,该实现针对接受数据范围进行了优化。您可以添加所有值,然后引发单个事件,而不是针对每个更改引发更改通知,然后绑定引擎将每个更改作为单个更新进行处理。此事件可以具有重设<代码>的重锤,也可以提供更改的项目及其从中更改的索引

这是一个在其
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);
            }
        }
    }
}

这里的成本通常是由于每个单独的添加都会引起更改通知。更可取的做法是创建一个新的集合实现,该实现针对接受数据范围进行了优化。您可以添加所有值,然后引发单个事件,而不是针对每个更改引发更改通知,然后绑定引擎将每个更改作为单个更新进行处理。此事件可以具有重设<代码>的重锤,也可以提供更改的项目及其从中更改的索引

这是一个在其
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);
            }
        }
    }
}

您可以在此处看到AddRange方法实现(对于列表):

您可以在此处看到AddRange方法的实现(对于列表):

多次调用
Add
会导致多次引发
INotifyCollectionChanged
,通常会导致UI重新绘制自身

虽然Lee的回答在技术上是正确的,即在添加所有项后引发
Reset
事件是正确的方法,但我从经验中发现,许多网格控件(例如)并不积极支持
Reset
事件

最普遍支持的选项是从
ObservableCollection
修改集合,并重新创建
ObservableCollection
属性本身

换句话说,您的
可观察收集
在您的虚拟机上定义如下

private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items {
    get { return _items;}
    set 
    { 
        _items = value;
        OnPropertyChanged(()=> Items);
    }
}

关于此技术,需要记住的另一件事是它是线程安全的,因为如果重新创建
可观测集合
,则可以从后台线程向
可观测集合
添加项目。正常的
observedcollection
不能通过非调度程序线程的
Add
方法向其添加项。

多次调用
Add
会导致多次引发
INotifyCollectionChanged
,通常会导致UI重新绘制自身

虽然Lee的回答在技术上是正确的,即在添加所有项后引发
Reset
事件是正确的方法,但我从经验中发现,许多网格控件(例如)并不积极支持
Reset
事件

最普遍支持的选项是从
ObservableCollection
修改集合,并重新创建
ObservableCollection
属性本身

换句话说,您的
可观察收集
在您的虚拟机上定义如下

private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items {
    get { return _items;}
    set 
    { 
        _items = value;
        OnPropertyChanged(()=> Items);
    }
}

关于此技术,需要记住的另一件事是它是线程安全的,因为如果重新创建
可观测集合
,则可以从后台线程向
可观测集合
添加项目。正常的
ObservableCollection
不能通过非调度程序线程的
Add
方法向其添加项。

这是因为每次向集合添加项时,ObservableCollection都会触发PropertyChanged事件。防止在批量添加项目时触发此事件是您希望看到的。这是一个优雅的解决方案,尽管我自己没有尝试过


这是因为每次向集合中添加项时,ObservableCollection都会触发PropertyChanged事件。防止在批量添加项目时触发此事件是您希望看到的。这是一个优雅的解决方案,尽管我自己没有尝试过


此线程中已经有一个可接受的答案,但对于所有正在寻找ObserverAgeCollection支持AddRange和ReplaceRange以及single CollectionChanged通知的良好实现的人,我真的建议。

此线程中已经有一个可接受的答案,但对于所有正在寻找ObserverAgeCollection的良好实现的人,我真的建议他们使用single CollectionChanged notification来支持AddRange和ReplaceRange。

由于您的代码有效,所以这个问题应该发布在上,因为您的代码有效,所以这个问题应该发布在Thank Lee上,不管怎样,我很难弄清楚什么是道歉。更新为完整且独立。谢谢李,无论如何,我有点麻烦弄清楚什么是NotifyEventComparerApologies。更新为完整且独立。嗨,Oleksandr,我已经看到了list.cs中的实现,但是它需要太多的私有字段和太多的代码:DHi Oleksandr,我已经看到了list.cs中的实现