C# ObservableCollection:调用OnCollectionChanged时包含多个新项

C# ObservableCollection:调用OnCollectionChanged时包含多个新项,c#,generics,c#-4.0,subclass,observablecollection,C#,Generics,C# 4.0,Subclass,Observablecollection,请注意,我正在尝试使用NotifyCollectionChangedAction.Add操作而不是.Reset。后者确实有效,但对于大型集合来说效率不是很高 所以我将ObservableCollection子类化: public class SuspendableObservableCollection<T> : ObservableCollection<T> 公共类SuspendableObservableCollection:ObservableCollection

请注意,我正在尝试使用NotifyCollectionChangedAction.Add操作而不是.Reset。后者确实有效,但对于大型集合来说效率不是很高

所以我将ObservableCollection子类化:

public class SuspendableObservableCollection<T> : ObservableCollection<T>
公共类SuspendableObservableCollection:ObservableCollection
出于某种原因,此代码:

private List<T> _cachedItems;
...

    public void FlushCache() {
        if (_cachedItems.Count > 0) {

        foreach (var item in _cachedItems)
            Items.Add(item);

        OnCollectionChanged(new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Add, (IList<T>)_cachedItems));
        }
    }
private List\u cachedItems;
...
公共void FlushCache(){
如果(_cachedItems.Count>0){
foreach(缓存数据项中的变量项)
项目。添加(项目);
OnCollectionChanged(新建NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,(IList)u cachedItems);
}
}
正在投掷 集合添加事件指的是不属于集合的项目

这似乎是BCL中的一个错误

在调用OnCollectionChanged之前,我可以单步执行并查看是否已将新项添加到此。项

刚刚有了一个惊人的发现。这些方法都不适用于我(flush、addrange),因为只有当此集合绑定到我的Listview时才会触发错误

TestObservableCollection<Trade> testCollection = new TestObservableCollection<Trade>();
List<Trade> testTrades = new List<Trade>();

for (int i = 0; i < 200000; i++) 
    testTrades.Add(t);

testCollection.AddRange(testTrades); // no problems here.. 
_trades.AddRange(testTrades); // this one is bound to ListView .. BOOOM!!!
TestObservableCollection testCollection=新的TestObservableCollection();
List testTrades=新列表();
对于(int i=0;i<200000;i++)
添加(t);
testCollection.AddRange(testTrades);//这里没问题。。
_trades.AddRange(testTrades);//这个绑定到ListView。。嘘!!!

总之,ObservableCollection确实支持添加增量列表,但ListView不支持。Andyp想出了一个解决方法,使其能够在下面的CollectionView中工作,但由于调用了.Refresh(),这与只调用OnCollectionChanged(.Reset)没有什么不同

我认为您需要将其转换为
IList

base.OnCollectionChanged(新的NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,(IList)\u cachedItems))

您可以为ObservableCollection实现AddRange(),如下所示:

公共类RangeObservableCollection:ObservableCollection
{
私人bool\u通知;
公共覆盖事件NotifyCollectionChangedEventHandler CollectionChanged;
CollectionChangedMultiItem上受保护的虚拟无效(
NotifyCollectionChangedEventArgs(e)
{
NotifyCollectionChangedEventHandler处理程序=this.CollectionChanged;
if(处理程序!=null)
{
中的foreach(NotifyCollectionChangedEventHandler处理程序
handlers.GetInvocationList())
{
if(handler.Target是CollectionView)
((CollectionView)handler.Target).Refresh();
其他的
处理者(本,e);
}
}
}
CollectionChanged上的受保护覆盖无效(NotifyCollectionChangedEventArgs e)
{
如果(!\u禁止通知)
{
基础。变更的集合(e);
如果(CollectionChanged!=null)
CollectionChanged.Invoke(this,e);
}
}
public void AddRange(IEnumerable列表)
{
if(list==null)
抛出新的ArgumentNullException(“列表”);
_SuppressNotification=true;
foreach(列表中的T项)
{
增加(项目);
}
_SuppressNotification=false;
OnCollectionChangedMultiItem(新的NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,list));
}
}
更新:绑定到ListBox后,我也看到了InvalidOperationException(与您看到的消息相同)。根据这一点,这是因为CollectionView不支持范围操作。幸运的是,本文还提供了一个解决方案(尽管感觉有点“黑客化”)


更新2:添加了一个修复程序,在OnCollectionChanged()的重写实现中引发重写的CollectionChanged事件。

感谢您的启发和YP。我对您的实现有一些问题,例如在测试中使用CollectionView而不是ICollectionView,以及手动调用元素上的“Reset”。从CollectionView继承的元素实际上可能会以比调用“this.Reset()”更多的方式处理这些参数,因此最好还是激发它们的处理程序,只使用它们所需的Action=Reset参数,而不是包含更改项列表的改进的事件参数。下面是我(非常类似)的实现

public class BaseObservableCollection<T> : ObservableCollection<T>
{
    //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
    private bool _SuppressCollectionChanged = false;

    /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    public BaseObservableCollection() : base(){}
    public BaseObservableCollection(IEnumerable<T> data) : base(data){}

    #region Event Handlers
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if( !_SuppressCollectionChanged )
        {
            base.OnCollectionChanged(e);
            if( CollectionChanged != null )
                CollectionChanged.Invoke(this, e);
        }
    }

    //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
    //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
    //for applications in code, so we actually check the type we're notifying on and pass a customized event args.
    protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if( handlers != null )
            foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
                handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    #endregion

    #region Extended Collection Methods
    protected override void ClearItems()
    {
        if( this.Count == 0 ) return;

        List<T> removed = new List<T>(this);
        _SuppressCollectionChanged = true;
        base.ClearItems();
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    public void Add(IEnumerable<T> toAdd)
    {
        if( this == toAdd )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toAdd )
            Add(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
    }

    public void Remove(IEnumerable<T> toRemove)
    {
        if( this == toRemove )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toRemove )
            Remove(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
    }
    #endregion
}
公共类BaseObservableCollection:ObservableCollection
{
//用于防止OnCollectionChanged在诸如Add(IEnumerable)和Clear()之类的批量操作期间触发的标志
private bool\u SuppressCollectionChanged=false;
///重写,以便我们可以手动调用已注册的处理程序,并区分需要和不需要操作的处理程序。重置args。
公共覆盖事件NotifyCollectionChangedEventHandler CollectionChanged;
public BaseObservableCollection():base(){}
公共BaseObservableCollection(IEnumerable数据):基(数据){}
#区域事件处理程序
CollectionChanged上的受保护覆盖无效(NotifyCollectionChangedEventArgs e)
{
如果(!\u SuppressCollectionChanged)
{
基础。变更的集合(e);
如果(CollectionChanged!=null)
CollectionChanged.Invoke(this,e);
}
}
//CollectionViews在向其传递NotifyCollectionChangedEventArgs时引发错误,该NotifyCollectionChangedEventArgs指示超过
//已添加或删除一个元素。他们希望收到“Action=Reset”通知,但这不合适
//对于代码中的应用程序,我们实际上会检查通知的类型,并传递自定义事件args。
CollectionChangedMultiItem上受保护的虚拟无效(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler处理程序=this.CollectionChanged;
如果(ha)
public class BaseObservableCollection<T> : ObservableCollection<T>
{
    //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
    private bool _SuppressCollectionChanged = false;

    /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    public BaseObservableCollection() : base(){}
    public BaseObservableCollection(IEnumerable<T> data) : base(data){}

    #region Event Handlers
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if( !_SuppressCollectionChanged )
        {
            base.OnCollectionChanged(e);
            if( CollectionChanged != null )
                CollectionChanged.Invoke(this, e);
        }
    }

    //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
    //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
    //for applications in code, so we actually check the type we're notifying on and pass a customized event args.
    protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if( handlers != null )
            foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
                handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    #endregion

    #region Extended Collection Methods
    protected override void ClearItems()
    {
        if( this.Count == 0 ) return;

        List<T> removed = new List<T>(this);
        _SuppressCollectionChanged = true;
        base.ClearItems();
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    public void Add(IEnumerable<T> toAdd)
    {
        if( this == toAdd )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toAdd )
            Add(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
    }

    public void Remove(IEnumerable<T> toRemove)
    {
        if( this == toRemove )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toRemove )
            Remove(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
    }
    #endregion
}
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
    private bool suppressNotification;

    public ObservableRangeCollection() { }

    public ObservableRangeCollection(IEnumerable<T> items)
        : base(items)
    {
    }

    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    protected virtual void OnCollectionChangedMultiItem(
        NotifyCollectionChangedEventArgs e)
    {
        var handlers = CollectionChanged;
        if (handlers == null) return;

        foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList())
        {
            if (handler.Target is ReadOnlyObservableCollection<T>
                && !(handler.Target is ReadOnlyObservableRangeCollection<T>))
            {
                throw new NotSupportedException(
                    "ObservableRangeCollection is wrapped in ReadOnlyObservableCollection which might be bound to ItemsControl " +
                    "which is internally using ListCollectionView which does not support range actions.\n" +
                    "Instead of ReadOnlyObservableCollection, use ReadOnlyObservableRangeCollection");
            }
            var collectionView = handler.Target as ICollectionView;
            if (collectionView != null)
            {
                collectionView.Refresh();
            }
            else
            {
                handler(this, e);
            }
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (suppressNotification) return;

        base.OnCollectionChanged(e);
        if (CollectionChanged != null)
        {
            CollectionChanged.Invoke(this, e);
        }
    }

    public void AddRange(IEnumerable<T> items)
    {
        if (items == null) return;

        suppressNotification = true;

        var itemList = items.ToList();

        foreach (var item in itemList)
        {
            Add(item);
        }
        suppressNotification = false;

        if (itemList.Any())
        {
            OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, itemList));
        }
    }

    public void AddRange(params T[] items)
    {
        AddRange((IEnumerable<T>)items);
    }

    public void ReplaceWithRange(IEnumerable<T> items)
    {
        Items.Clear();
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        AddRange(items);
    }

    public void RemoveRange(IEnumerable<T> items)
    {
        suppressNotification = true;

        var removableItems = items.Where(x => Items.Contains(x)).ToList();

        foreach (var item in removableItems)
        {
            Remove(item);
        }

        suppressNotification = false;

        if (removableItems.Any())
        {
            OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removableItems));
        }
    }
}

public class ReadOnlyObservableRangeCollection<T> : ReadOnlyObservableCollection<T>
{
    public ReadOnlyObservableRangeCollection(ObservableCollection<T> list)
        : base(list)
    {            
    }

    protected override event NotifyCollectionChangedEventHandler CollectionChanged;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        var handlers = CollectionChanged;
        if (handlers == null) return;

        foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList())
        {
            var collectionView = handler.Target as ICollectionView;
            if (collectionView != null)
            {
                collectionView.Refresh();
            }
            else
            {
                handler(this, e);
            }
        }
    }
}