Вопрос

Допустим, я хочу создать класс коллекции, который по умолчанию является потокобезопасным.

Внутренне класс имеет защищенный List<T> свойство, называемое Values.

Для начала, имеет смысл иметь реализацию класса ICollection<T>.Некоторые элементы этого интерфейса довольно просты в реализации;например, Count ВОЗВРАТ this.Values.Count.

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

Конечно, я всегда мог бы бросить NotSupportedException на IEnumerable<T>.GetEnumerator и IEnumerable.GetEnumerator, но для меня это похоже на отговорку.

У меня уже есть потокобезопасный getValues функция, которая фиксируется на Values и возвращает копию в виде T[] массив.Итак, моя идея состояла в том, чтобы реализовать GetEnumerator возвращая this.getValues().GetEnumerator() так что следующий код действительно был бы потокобезопасным:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

foreach (T value in coll) {
    // do something with value
}

К сожалению, эта реализация, похоже, работает только для IEnumerable.GetEnumerator, а не универсальная версия (и поэтому приведенный выше код выдает InvalidCastException).

Одна из моих идей, которая, казалось, сработала, состояла в том, чтобы разыграть T[] возвращаемое значение из getValues к одному IEnumerable<T> перед вызовом GetEnumerator на нем.Альтернативой было бы изменить getValues чтобы вернуть IEnumerable<T> в первую очередь, но затем для неродовых IEnumerable.GetEnumerator просто приведите возвращаемое значение из getValues к непатентованному IEnumerable.Но я действительно не могу решить, кажутся ли эти подходы небрежными или вполне приемлемыми.

В любом случае, есть ли у кого-нибудь идея получше о том, как это сделать?Я слышал о .Synchronized методы, но они, похоже, доступны только для неродовых коллекций в System.Collections пространство имен.Может быть, есть общий вариант этого, который уже существует в .NET, о котором я просто не знаю?

Это было полезно?

Решение

В большинстве коллекций указано, что вы не можете добавлять или удалять элементы из коллекции во время итерации.Из этого следует, что для потокобезопасной коллекции вы хотите запретить другим потокам изменять коллекцию во время итерации любого потока.Это должно быть легко сделать с помощью синтаксиса итераторов и не требовать от вас создания копии:

public IEnumerator<T> GetEnumerator()
{
    lock (this.Values) // or an internal mutex you use for synchronization
    {
        foreach (T val in this.Values)
        {
            yield return val;
        }
    }
    yield break;
}

Это предполагает, что все другие операции, которые могут изменять коллекцию, также блокируют this.Values.Пока это правда, с тобой все должно быть в порядке.

Другие советы

К сожалению, эта реализация, похоже, работает только для IEnumerable.GetEnumerator, а не универсальная версия (и поэтому приведенный выше код выдает исключение InvalidCastException).

Мне это кажется странным.Реализовали ли вы явно непатентованный IEnumerable?То есть.вы уже писали

public IEnumerator<T> GetEnumerator() { ...}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator<T>(); }

Кроме того, пытались ли вы реализовать IEnumerable с синтаксисом итераторов?Это должно быть легко:

public IEnumerator<T> GetEnumerator()
{
    T[] values;
    lock(this.Values)
        values = this.Values.ToArray();
    foreach(var value in values)
        yield return value;
}

Если вы пробовали, почему это не соответствует вашим потребностям?

Тип T[] имеет особое отношение к типу System.Array из которого это вытекает.Массивы , подобные T[] были созданы до появления дженериков в .NET (в противном случае синтаксис мог бы быть Array<T>).

Тип System.Array имеет один GetEnumerator() метод экземпляра, public.Этот метод возвращает неродовой IEnumerator (начиная с .NET 1).И System.Array не имеет явных реализаций интерфейса для GetEnumerator().Это объясняет ваши наблюдения.

Но когда дженерики были введены в .NET 2.0, был сделан специальный "взлом", чтобы иметь T[] реализовать IList<T> и его базовые интерфейсы (из которых IEnumerable<T> является единым целым).По этой причине я думаю, что вполне разумно использовать:

((IList<T>)(this.getValues())).GetEnumerator()

В версии .NET, где я проверяю это, существуют следующие классы:

namespace System
{
  public abstract class Array
  {
    private sealed class SZArrayEnumerator  // instance of this is returned with standard GetEnumerator() on a T[]
    {
    }
  }

  internal sealed class SZArrayHelper
  {
    private sealed class SZGenericArrayEnumerator<T>  // instance of this is returned with generic GetEnumerator() on a T[] which has been cast to IList<T>
    {
    }
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top