BlockReentrancy в ObservableCollection <T> - PullRequest
23 голосов
/ 06 июня 2011

Может быть, кто-нибудь проявит любезность и объяснит мне, для чего предназначен метод BlockReentrancy в ObservableCollection<T>?

MSDN , в качестве примера приведено следующее:

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

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

Но это, кажется, не проясняет для меня, какова цель.Кто-нибудь хочет объяснить?

Ответы [ 4 ]

26 голосов
/ 06 июня 2011

ObservableCollection реализует INotifyCollectionChanged и поэтому имеет событие CollectionChanged. Если на это событие есть подписчик, он может дополнительно изменить коллекцию, пока коллекция уже находится в процессе уведомления. Поскольку событие CollectionChanged отслеживает, что именно изменилось, это взаимодействие может стать очень запутанным.

В результате ObservableCollection позволяет, в особом случае, одному подписчику события CollectionChanged изменять коллекцию из своего обработчика. Но запрещает изменять коллекцию из обработчика CollectionChanged , если есть два или более подписчиков на событие CollectionChanged.

Пара методов BlockReentrancy и CheckReentancy используются для реализации этой логики. BlockReentrancy используется в начале метода OnCollectionChanged, а CheckReentancy используется во всех методах, которые изменяют коллекцию.

11 голосов
/ 06 июня 2011

Это реализация BlockReentrancy()

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

Есть еще один метод CheckReentrancy()

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

Такие методы как ClearItems, InsertItem, MoveItem, RemoveItem, SetItem проверяют CheckReentrancy() перед изменением коллекции.

Таким образом, приведенный ниже код гарантирует, что коллекция не будет изменена внутри using, но только в том случае, если на событие CollectionChanged подписано более одного обработчика.

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

Этот пример демонстрирует эффект 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)
{
}
3 голосов
/ 06 июня 2011

Повторный вход - это когда метод делает что-то прямо или косвенно, что вызывает его повторный вызов, возможно, рекурсивно. В этом случае блок using должен использоваться внутри делегата OnCollectionChanged, если вы хотите предотвратить изменение коллекции из обработчика; попытки изменить его приведут к исключению. Если вы его не используете, то любые попытки изменить коллекцию вызовут повторный вызов OnCollectionChanged.

0 голосов
/ 18 января 2019

Ниже приведен код за BlockReentrancy.CheckReentrancy вызывается в начале каждого метода модификатора коллекции в реализации 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)

...