videokojot
videokojot

Reputation: 541

Explicit IEnumerator<T> implementation VS yield return implementation

I have following problem:

I want to implement my own collection, which will also implement ICollection<T> interface. That means that I also need to implement IEnumerable<T> interface. Implementing IEnumerable<T> can be done two ways:

First approach: by having private struct implementing IEnumerator<T> and returning it from GetEnumerator() method

Second approach: I can just use iterator (using yield return) and let compiler generate IEnumerator class for me.

I also want my enumerators to throw InvalidOperationException on any MoveNext() (or Reset()) call after the collection was modified as it is specified in MS documentation

If I will use first approach, then everything is ok (I just save version of my collection when creating new enumerator and then in MoveNext() I just check if it hasnt changed). The same trick is used by MS Collections But here is THE PROBLEM - if I will use second approach I think I cannot enforce following behaviour. Consider following code.

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... :(

This problem occurs only if the collection is modified after obataining enumerator object, but before first call to MoveNext(), but still it is problem...

My question is: is it possible somehow use second approach AND enforce right behaviour OR I need for this case to stick with first approach ?

My suspicion is that I have to use first approach, since the code of iterator method is exeuted only when I call MoveNext() , and not in constructor of compiler generated class, but I want to be sure...

Upvotes: 3

Views: 576

Answers (1)

SLaks
SLaks

Reputation: 887275

Your problem is that an iterator method won't run any code at all until the first MoveNext() call.

You can work around this problem by wrapping the iterator in a non-iterator method that grabs the version immediately and passes it as a parameter:

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

private IEnumerator<T> GetRealEnumerator(int baseVersion) {

Upvotes: 1

Related Questions