Возможно ли получить IEnumerator<T> из T[]?
-
21-09-2019 - |
Вопрос
Допустим, я хочу создать класс коллекции, который по умолчанию является потокобезопасным.
Внутренне класс имеет защищенный 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>
{
}
}
}