My previous blog post went into some detail as to why calling MoveNext
on a BCL generic collection enumerator didn’t quite do what you thought it would. This post covers the Reset
method.
To recap, here’s the simple wrapper around a linked list enumerator struct from my previous post (minus the readonly
on the enumerator variable):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
sealed class EnumeratorWrapper : IEnumerator<int> { private LinkedList<int>.Enumerator m_Enumerator; public EnumeratorWrapper(LinkedList<int> linkedList) { m_Enumerator = linkedList.GetEnumerator(); } public int Current { get { return m_Enumerator.Current; } } object System.Collections.IEnumerator.Current { get { return Current; } } public bool MoveNext() { return m_Enumerator.MoveNext(); } public void Reset() { ((System.Collections.IEnumerator)m_Enumerator).Reset(); } public void Dispose() { m_Enumerator.Dispose(); } } |
If you have a look at the Reset
method, you’ll notice I’m having to cast to IEnumerator
to be able to call Reset
on m_Enumerator
. This is because the implementation of LinkedList<int>.Enumerator.Reset
, and indeed of all the other Reset
methods on the BCL generic collection enumerators, is an explicit interface implementation.
However, IEnumerator
is a reference type. LinkedList<int>.Enumerator
is a value type. That means, in order to call the reset method at all, the enumerator has to be boxed. And the IL confirms this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.method public hidebysig newslot virtual final instance void Reset() cil managed { .maxstack 8 L_0000: nop L_0001: ldarg.0 L_0002: ldfld valuetype [System]System.Collections.Generic.LinkedList`1/Enumerator<int32> EnumeratorWrapper::m_Enumerator L_0007: box [System]System.Collections.Generic.LinkedList`1/Enumerator<int32> L_000c: callvirt instance void [mscorlib]System.Collections.IEnumerator::Reset() L_0011: nop L_0012: ret } |
On line 0007, we’re doing a box
operation, which copies the enumerator to a reference object on the heap, then on line 000c calling Reset
on this boxed object. So m_Enumerator
in the wrapper class is not modified by the call the Reset
. And this is the only way to call the Reset
method on this variable (without using reflection). It is impossible to reset a BCL enumerator used as an unboxed value type.
Therefore, the only way that the collection enumerator struct can be used safely is to store them as a boxed IEnumerator<T>
, and not use them as value types at all.
Load comments