Подтвердить что ты не робот

Почему ConcurrentBag <T> не реализует ICollection <T>?

У меня есть метод, который принимает IList < > и добавляет к нему материал. Я хотел бы передать его ConcurrentBag в некоторых случаях, но он не реализует IList < > или ICollection < > , только не общий ICollection, который не имеет метода Add.

Теперь я понимаю, почему он не может (возможно) реализовать IList - это не упорядоченная коллекция, поэтому для него не имеет смысла иметь индекс. Но я не вижу проблемы с любым методом ICollection < > .

Итак, почему? А также - есть ли поточно-безопасная коллекция в .NET, которая реализует более надежные интерфейсы?

4b9b3361

Ответ 1

A List<T> не является параллельным и поэтому может реализовать ICollection<T>, который дает вам пару методов Contains и Add. Если Contains возвращает false, вы можете безопасно позвонить Add, зная, что он будет успешным.

A ConcurrentBag<T> является параллельным и поэтому он не может реализовать ICollection<T>, потому что ответ Contains возвращает может быть недействительным к моменту вызова Add. Вместо этого он реализует IProducerConsumerCollection<T>, который предоставляет единственный метод TryAdd, который выполняет работу как Contains, так и Add.

Так что, к сожалению, вы хотите работать над двумя вещами, которые являются коллекциями, но не имеют общего интерфейса. Есть много способов решить эту проблему, но мой предпочтительный подход, когда API так же схож, как и в том, чтобы обеспечить перегрузку методов для обоих интерфейсов, а затем использовать лямбда-выражения для создания делегатов, которые выполняют одну и ту же операцию для каждого интерфейса, используя свои собственные методы. Затем вы можете использовать этот делегат вместо того, где вы выполнили почти общую операцию.

Вот простой пример:

public class Processor
{
    /// <summary>
    /// Process a traditional collection.
    /// </summary>
    /// <param name="collection">The collection.</param>
    public void Process(ICollection<string> collection)
    {
        Process(item =>
            {
                if (collection.Contains(item))
                    return false;
                collection.Add(item);
                return true;
            });
    }

    /// <summary>
    /// Process a concurrent collection.
    /// </summary>
    /// <param name="collection">The collection.</param>
    public void Process(IProducerConsumerCollection<string> collection)
    {
        Process(item => collection.TryAdd(item));
    }

    /// <summary>
    /// Common processing.
    /// </summary>
    /// <param name="addFunc">A func to add the item to a collection</param>
    private void Process(Func<string, bool> addFunc)
    {
        var item = "new item";
        if (!addFunc(item))
            throw new InvalidOperationException("duplicate item");
    }
}

Ответ 2

Не то, чтобы ConcurrentBag<T> не смог реализовать ICollection<T>; вы можете себе представить, что Contains можно реализовать с помощью TryPeek или Remove с помощью TryTake.

Проблема заключается в том, что обработка ConcurrentBag<T> как ICollection<T> (например, разрешая неявное преобразование при передаче ConcurrentBag<T> методу, который принимает только ICollection<T>), будет неразумным, поскольку большинство потребителей ICollection<T> ожидать, что у него будет существенно отличающаяся семантика от ConcurrentBag<T>.

Большинство методов, которые принимают параметр ICollection<T> в качестве параметра, скорее всего, сделают предположения (безопасные в однопоточном сценарии), такие как "Add, за которым следует Contains, всегда будут возвращать true", или "if Contains возвращает true, поэтому будет Remove". Однако в многопоточных ситуациях (в которых, вероятно, в первую очередь используется ConcurrentBag<T>), эти допущения вряд ли будут иметь место. Это может привести к ошибкам в коде, который был написан с предположением использования ICollection<T> в однопоточном сценарии.

Если вам действительно нужно выставить ConcurrentBag<T> как ICollection<T> (и вы знаете, что код, который вы передаете ему, ожидает, что он будет работать не-t21), он должен быть довольно простым написать класс оболочки (который использует шаблон адаптера ), чтобы имитировать методы ICollection<T>, используя самые доступные методы на ConcurrentBag<T>.

Ответ 3

Там SynchronizedCollection<T> реализует как IList<T>, так и ICollection<T>, а также IEnumerable<T>.