18

From what I saw on http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx, and article by Jon Skeet, the c# specification itself says that. What would be the reason?

devoured elysium
  • 101,373
  • 131
  • 340
  • 557

3 Answers3

13

It is impossible to support properly in all sequences; many are once only (network streams, etc). And if you can't rely on it all the time, it is useless, as the abstraction is broken. Sure you could have an IResettableEnumerator, but Reset() on IEnumerator doesn't work. Frankly, it was a mistake (IMO).

I suspect it would also have made iterator blocks even more complicated (they are currently one of the two most complex parts of the compiler; I can't remember which is "top"; them, or anonymous methods / captured variables).

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 3
    Well, both iterator blocks and anonymous functions are complicated because they capture local variables and hoist them onto a closure class. Which is worse is a bit of a toss-up. Anonymous functions nest, which make them complex, and lambdas have a complex interaction with the type system. Iterators have a complicated codegen strategy involving creating "disposers" to handle finally blocks. So, who knows? They're both a pain to get right. – Eric Lippert Sep 23 '09 at 20:16
  • 6
    The other reason why this was a design mistake is that it is unnecessary; if you want to reset to the beginning, just create a fresh new iterator object. Iterators are cheap; there's no benefit to reusing them. To make resetting useful, what we really more is the ability to "snapshot" an iterator at a particular point, so that you can go forward on one copy, and then "back up to where you were". – Eric Lippert Sep 23 '09 at 20:18
  • 1
    I suspect that level of detail more than warrants an independent answer... but thanks for filling in some blanks. – Marc Gravell Sep 23 '09 at 20:23
  • 1
    If I understand correctly, the sole reason for existence of `IEnumerator.Reset` in .NET 1.0 was so that it could wrap COM enumerators like `IEnumVARIANT`, which typically have `Reset` (and which is potentially optimized). – Pavel Minaev Sep 24 '09 at 07:32
  • Its not necessarily "broken", just possibly multiple abstractions combined into one. Look at C++, there are six different concepts related to iterators. http://www.sgi.com/tech/stl/Iterators.html – cdiggins Oct 28 '09 at 03:09
13

That's not how I read the C# spec [Word doc]. Section 10.14.4 "Enumerator objects", states:

...[E]numerator objects do not support the IEnumerator.Reset method. Invoking this method causes a System.NotSupportedException to be thrown.

However, this section (and statement) is specific to "enumerator objects", which is defined as:

When a function member returning an enumerator interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. Instead, an enumerator object is created and returned.

In other words, an "enumerator object" is a compiler generated IEnumerator1. There's no restrictions on every IEnumerator, just the ones generated from iterator blocks (aka yield).

As for why? I'd suspect because it's somewhat impossible to do in the general case - without saving every value and the consequent memory limitations of that. Combine that with the fact that IEnumerator.Reset() is rarely used (when's the last time that you Reset an enumerator?) and that MSDN specifically calls out that it need not be implemented:

The Reset method is provided for COM interoperability. It does not necessarily need to be implemented; instead, the implementer can simply throw a NotSupportedException.

and you get to cut out a lot of complexity without anyone really noticing.

As for requiring that it throw2, I suppose it's just simpler than letting the implementor decide. IMO, it's a bit much to require the throw - there may be reasonable cases that a compiler (or other implementation1) could generate a Reset method for, but I don't see it as being a real problem either.

1 Technically, the spec leaves open the possibility of other implementations:

An enumerator object is typically an instance of a compiler-generated enumerator class that encapsulates the code in the iterator block and implements the enumerator interfaces, but other methods of implementation are possible.

but I'm not aware of any other concrete implementations. Regardless, to be compliant, other implementations of an "enumerator object" would have to throw NotSupportedException as well.

2 Nitpicker's corner: I think there may be some quibble even in the "requirement" to throw. The spec, in not using the preferred "MUST, SHOULD, MAY" verbiage, leaves it a bit open. I read "causes" more as a note of implementation - not a requirement. Then again, I haven't read the entire spec, so perhaps they define these terms a bit more or are more explicit somewhere else.

Mark Brackett
  • 84,552
  • 17
  • 108
  • 152
  • "The Reset method is provided for COM interoperability." Can you please explain this further? What is COM interoperability and why would we care about it? – David Klempfner Mar 27 '21 at 10:48
4

Here's what MSDN says

The Reset method is provided for COM interoperability. It does not necessarily need to be implemented; instead, the implementer can simply throw a NotSupportedException.

http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx>MSDN IEnumerator..::.Reset Method

It doesn't say it must, it just says it can.

EDIT: However as Marc has pointed out, there is a difference in the C# 2.0 Spec

22.2 Enumerator objects

Note that enumerator objects do not support the IEnumerator.Reset method. Invoking this method causes a System.NotSupportedException to be thrown.

ParmesanCodice
  • 5,017
  • 1
  • 23
  • 20