Явная реализация IEnumerator‹T› VS реализация yield return

У меня следующая проблема:

Я хочу реализовать свою собственную коллекцию, которая также будет реализовывать интерфейс ICollection<T>. Это означает, что мне также нужно реализовать интерфейс IEnumerable<T>. Реализовать IEnumerable<T> можно двумя способами:

Первый подход: с помощью частной структуры, реализующей IEnumerator<T> и возвращающей ее из метода GetEnumerator().

Второй подход: я могу просто использовать итератор (используя yield return) и позволить компилятору сгенерировать для меня класс IEnumerator.

Я также хочу, чтобы мои счетчики выдавали InvalidOperationException при любом вызове MoveNext() (или Reset()) после изменения коллекции как указано в документации MS

Если я буду использовать первый подход, то все в порядке (я просто сохраняю версию своей коллекции при создании нового счетчика, а затем в MoveNext() просто проверяю, не изменилась ли она). Тот же прием используется MS Collections. Но вот < strong>ПРОБЛЕМА - если я буду использовать второй подход, я думаю, что не смогу обеспечить следующее поведение. Рассмотрим следующий код.

class MyCollection : ICollcetion<T>
{
int _version = 0;
T[] _collectionData;
public void Modify()
  {
    ... // do the modification
    _version++; // change version, so enumerators can check that they are invalid
  }

...

public IEnumerator<T> GetEnumerator()
  {
    int _enmVersion = _version;
    int i = 0;
    yield return _collectionData[i];
    if( _enmVersion != _version ) // check if there was not modificaction
    {
      throw new InvalidOperationException();
    }
  }

...

}

...

var collection = new MyCollection(); 
var e = collection.GetEnumerator(); 
myCollection.Modfiy(); //collection modification, so e becomes irrecoverably invalidated
e.MoveNext(); // right now I want to throw exception
              //but instead of that, code until first yield return executed
              //and the version is saved... :(

Эта проблема возникает только в том случае, если коллекция модифицируется после получения объекта перечислителя, но до первого вызова MoveNext(), но все же это проблема...

Мой вопрос: возможно ли как-то использовать второй подход И обеспечить правильное поведение ИЛИ мне нужно в этом случае придерживаться первого подхода?

Я подозреваю, что мне нужно использовать первый подход, поскольку код метода итератора выполняется только тогда, когда я вызываю MoveNext() , а не в конструкторе сгенерированного компилятором класса, но я хочу быть уверен...


person videokojot    schedule 07.03.2016    source источник


Ответы (1)


Ваша проблема в том, что метод итератора вообще не запускает никакого кода до первого вызова MoveNext().

Вы можете обойти эту проблему, заключив итератор в не-итераторный метод, который немедленно получает версию и передает ее в качестве параметра:

public IEnumerator<T> GetEnumerator() {
    return GetRealEnumerator(_version);
}

private IEnumerator<T> GetRealEnumerator(int baseVersion) {
person SLaks    schedule 07.03.2016
comment
Большое спасибо, это работает. Я думал, что сигнатура метода итератора должна быть GetEnumerator(), чтобы ее заметил компилятор. - person videokojot; 08.03.2016
comment
Нет; любой метод может быть итератором. Итераторы также могут возвращать IEnumerable<T>. - person SLaks; 08.03.2016