I have a following code in my graph library:
while(true) {
using (var i = ancestry.GetAdjacent(current).GetEnumerator())
{
if (!i.MoveNext())
yield break;
if (i.MoveNext())
throw new InvalidOperationException("ancestry graph can only have one adjacent vertex for any given vertex");
// it's a bug, as some enumerators could return null after failed MoveNext().
current = i.Current;
if (isSentinel(current))
yield break;
}
}
This code works for some instances of IEnumerator, and fails for others (all standard implementations from .NET, plus iterator methods with yield return.
Is that behaviour unspecified?
If not, what is the correct notation? Leave Current at previous, or set to default(T)?
UPDATE:
I have explored some of the implementations of IEnumerator for collections, Linq extensions as well as typed array: fiddle.
In my understanding, Linq violates the contract of IEnumerable, as it does not throw on Current beyond the end of enumeration, and typed array violates the contract of IEnumerable, as it throws on IEnumerable<T>.Current
, and there are no exception types specified for IEnumerable.Current.
Can someone clarify, what does it mean 'undefined' when it's stated in documentation? Does it include throwing unspecified exceptions:
From IEnumerator.Current Property documentation:
Current is undefined under any of the following conditions:
- The enumerator is positioned before the first element in the collection, immediately after the enumerator is created. MoveNext must be called to advance the enumerator to the first element of the collection before reading the value of Current.
- The last call to MoveNext returned false, which indicates the end of the collection.
- The enumerator is invalidated due to changes made in the collection, such as adding, modifying, or deleting elements. Current returns the same object until MoveNext is called. MoveNext sets Current to the next element.
The value of Current
is not defined, if MoveNext
returns false
, as per the contract of the interface, as is described in its documentation. You should never rely on any given behavior. You simply shouldn't ever check the value of Current
whenever MoveNext
is false
.
If
MoveNext
passes the end of the collection, the enumerator is positioned after the last element in the collection andMoveNext
returnsfalse
. When the enumerator is at this position, subsequent calls toMoveNext
also returnfalse
. If the last call toMoveNext
returnedfalse
,Current
is undefined.
(Note that IEnumerator
says that Current
should throw an exception in this situation, but in my experience most implementations don't bother to explicitly throw, they just expose an undefined value; this change is reflected in the newer IEnumerator<T>
interface's documentation.)