开发者

BlockReentrancy in ObservableCollection<T>

Could someone please be kind enough to explain to me what the purpose of the BlockReentrancy Method is in the ObservableCollection<T> ?

MSDN shows the following as an example:

//The typical usage is to wrap an OnCollectionChanged call within a using scope, as in the following example:

using (BlockReentrancy())
{
    // OnCollectionChanged call
}

But this doesn't seem to clarify for me what the purpose is. Anyone care to e开发者_StackOverflow中文版xplain?


An ObservableCollection implements INotifyCollectionChanged and so it has a CollectionChanged event. If there is a subscriber to this event, they could further modify the collection while the collection is already in the process of notification. Since the CollectionChanged event keeps track of exactly what changed, this interaction can get very messy.

As a result, the ObservableCollection allows, as a special case, a single subscriber of the CollectionChanged event to modify the collection from its handler. But it disallows modifying the collection from the CollectionChanged handler if there are two or more subscribers to the CollectionChanged event.

The pair of methods BlockReentrancy and CheckReentancy are used to implement this logic. The BlockReentrancy is used at the start of the OnCollectionChanged method and CheckReentancy is used in all methods that modify the collection.


This is implementation of BlockReentrancy()

protected IDisposable BlockReentrancy()
{
   this._monitor.Enter();
   return this._monitor;
}

There is another method CheckReentrancy()

protected void CheckReentrancy()
{
    if ((this._monitor.Busy && (this.CollectionChanged != null)) && (this.CollectionChanged.GetInvocationList().Length > 1))
    {
        throw new InvalidOperationException(SR.GetString("ObservableCollectionReentrancyNotAllowed"));
    }
}

Such methods as ClearItems, InsertItem, MoveItem, RemoveItem, SetItem check CheckReentrancy() before modifying collection.

So the code below guarantees that collection will not be changed inside of using, but only if there is more than one handler subscribed to CollectionChanged event.

using BlockReentrancy())
{
    CollectionChanged(this, e);
}

This example demonstrates effect of BlockReentrancy()

private static void Main()
{
    collection.CollectionChanged += CollectionCollectionChanged1;
    collection.CollectionChanged += CollectionCollectionChanged2;
    collection.Add(1);
}

private static void CollectionCollectionChanged1(object sender, NotifyCollectionChangedEventArgs e)
{
    collection.Add(2); // this line will throw exception
}

private static void CollectionCollectionChanged2(object sender, NotifyCollectionChangedEventArgs e)
{
}


Reentrancy is when a method does something directly or indirectly that causes that method to be invoked again, possibly recursively. In this case, the using block should be used inside the OnCollectionChanged delegate if you want to prevent the changing of the collection from within the handler; attempts to change it will throw an exception. If you didn't use it, then any attempts to modify the collection would cause OnCollectionChanged to be called again.


Below is the code behind BlockReentrancy. CheckReentrancy is called at the start of each collection modifier method in the implementation of ObservableCollection.

    /// <summary>
    /// Disallow reentrant attempts to change this collection. E.g. an event handler
    /// of the CollectionChanged event is not allowed to make changes to this collection.
    /// </summary>
    /// <remarks>
    /// typical usage is to wrap e.g. a OnCollectionChanged call with a using() scope:
    /// <code>
    ///         using (BlockReentrancy())
    ///         {
    ///             CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item, index));
    ///         }
    /// </code>
    /// </remarks>
    protected IDisposable BlockReentrancy()
    {
        _blockReentrancyCount++;
        return EnsureMonitorInitialized();
    }

    /// <summary> Check and assert for reentrant attempts to change this collection. </summary>
    /// <exception cref="InvalidOperationException"> raised when changing the collection
    /// while another collection change is still being notified to other listeners </exception>
    protected void CheckReentrancy()
    {
        if (_blockReentrancyCount > 0)
        {
            // we can allow changes if there's only one listener - the problem
            // only arises if reentrant changes make the original event args
            // invalid for later listeners.  This keeps existing code working
            // (e.g. Selector.SelectedItems).
            if (CollectionChanged?.GetInvocationList().Length > 1)
                throw new InvalidOperationException(SR.ObservableCollectionReentrancyNotAllowed);
        }
    }

    private SimpleMonitor EnsureMonitorInitialized()
    {
        return _monitor ?? (_monitor = new SimpleMonitor(this));
    }

(Copyright (c) .NET Foundation and Contributors)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜