Why enumerator structs are a really bad idea (redux)
Posted
by Simon Cooper
on Simple Talk
See other posts from Simple Talk
or by Simon Cooper
Published on Thu, 20 May 2010 10:13:00 GMT
Indexed on
2010/05/20
10:42 UTC
Read the original article
Hit count: 299
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):
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:
.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).
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.
© Simple Talk or respective owner