Перечисление SortedSet не работает должным образом - PullRequest
0 голосов
/ 02 апреля 2020

Допустим, ClassA - это случайный класс, и я создаю SortedSet of ClassA. Я храню перечисление в словаре, но всякий раз, когда я пытаюсь получить доступ, оно всегда дает ноль;

var set = new SortedSet<ClassA>();
set.Add(new ClassA());
var map = new Dictionary<int, SortedSet<ClassA>.Enumerator>();
map[1] = set.GetEnumerator();
map[1].MoveNext();
var val = map[1].Current;
//Why val is null ???

1 Ответ

3 голосов
/ 02 апреля 2020

Это происходит потому, что SortedSet<T>.Enumerator является struct. Каждый раз, когда вы используете индексатор словаря для извлечения перечислителя, вы получаете новую его копию. Поэтому, несмотря на то, что вы вызываете MoveNext() для этой копии, в следующий раз, когда вы получите копию перечислителя, она не будет иметь никакого значения для Current.

Интересно, из-за причуды в В точной реализации этой структуры каждая копия перечислителя получает один и тот же объект ссылочного типа для отслеживания состояния перечисления (стека), и поэтому метод MoveNext(), кажется, работает (то есть возвращает true при первом вызове, но false при любом последующем).

Существует как минимум четыре варианта для правильной обработки ...

Извлечение копии в переменную, и используйте переменную вместо словаря:

var set = new SortedSet<ClassA>();
set.Add(new ClassA());
var map = new Dictionary<int, SortedSet<ClassA>.Enumerator>();
map[1] = set.GetEnumerator();
var e = map[1];
e.MoveNext();
val = e.Current;

Обратите внимание, что в этом примере копия словаря перечислителя по-прежнему не будет иметь желаемого значения Current. Вам нужно будет снова установить копию словаря после вызова MoveNext(), чтобы сохранить это: map[1] = e;

Использовать массив вместо словаря:

var set = new SortedSet<ClassA>();
set.Add(new ClassA());
var map = new SortedSet<ClassA>.Enumerator[2];
map[1] = set.GetEnumerator();
map[1].MoveNext();
var val = map[1].Current;

Кроме различий в объявлении и инициализации map, это работает точно так же, как сейчас у вас есть код. Это связано с тем, что индексированные элементы массива являются переменными , вместо того, чтобы проходить через индексатор, как это делает синтаксический индекс для любой другой коллекции. Таким образом, вы работаете непосредственно с копией перечислителя, хранящейся в массиве, а не с копией fre sh, возвращаемой индексатором.

Конечно, это будет работать, только если значения ключей были достаточно ограничены, чтобы сделать возможным выделение массива, достаточно большого, чтобы вместить все возможности для ключа.

Объявить упаковку ссылочного типа, содержащую перечислитель типа значения:

class E<T>
{
    public SortedSet<T>.Enumerator Enumerator;

    public E(SortedSet<T>.Enumerator enumerator)
    {
        Enumerator = enumerator;
    }
}

then…

var set = new SortedSet<ClassA>();
set.Add(new ClassA());
var map = new Dictionary<int, E<ClassA>>();
map[1] = new E<ClassA>(set.GetEnumerator());
map[1].Enumerator.MoveNext();
val = map[1].Enumerator.Current;

В этом примере словарь просто возвращает ссылку на объект-оболочку, обеспечивая только одну копию перечислителя (которая хранится в этом объекте-оболочке, а не в словаре) , Таким образом, каждый раз, когда вы получаете доступ к объекту через словарь, вы получаете одну и ту же копию.

Конечно, вам придется go через поле Enumerator оболочки. Это немного неуклюже. Но это сработало бы.

Храните перечислители в массиве, но индексируйте через словарь:

var set = new SortedSet<ClassA>();
set.Add(new ClassA());
var map = new Dictionary<int, int>();
var a = new SortedSet<ClassA>.Enumerator[1];
map[1] = 0;
a[map[1]] = set.GetEnumerator();
a[map[1]].MoveNext();
val = a[map[1]].Current;

Это объединяет два предыдущих варианта. Массив используется для хранения фактических перечислителей, так что вы можете обращаться к ним как к переменным, но словарь используется как уровень косвенности для массива, так что вы можете обращаться к перечислителям через любые ключи, которые вам нравятся.

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

Дополнительная косвенность немного неуклюжа, как в опции оболочки, но не так плохо, и решает проблему размера массива опции на основе массива. Возможно, ваш вопрос является дубликатом этого вопроса: List.All (e => e.MoveNext ()) не перемещает мои счетчики на

Это определенно тесно связано с этим один, а также этот: Подробности о том, что происходит, когда структура реализует интерфейс

...