Enumerators and boxing..


Jeroen asked:


While we’re on the subject of boxing, why doesn’t foreach do the same optimization as using? foreach always seems to box the enumerator struct.


If you use a struct as an enumerator, you will always box when you go to IEnumerator. Interfaces are reference types, and you have to box to get an interface reference to a struct.


The fix is to implement the strongly-typed enumerator pattern. Start with the IEnumerable/IEnumerator version and:


1) Change the type of the Current property in the IEnumerator struct from object to the strong type.
2) Remove IEnumerator from the implementation list on the struct type.
3) Remove IEnumerable from the implementation list
4) Change GetEnumerator() so that it returns the struct type rather than IEnumerator


The compiler will then deal with the strongly-typed version. If you want to also allow the interface versions, you can implement them specifically.


Here’s some code (sorry about the formatting):

public class IntegerListExplicit: IEnumerable
{
int count = 0;
int allocated = 10;
int[] elements = new int[10];
public IntegerListExplicit()
{
}
void Expand()
{
if (count == allocated)
{
int[] newElements = new int[allocated * 2];
for (int i = 0; i < count; i++)
newElements[i] = elements[i];
allocated = allocated * 2;
elements = newElements;
}
}
public int Add (int item)
{
lock(this)
{
Expand();
elements[count] = item;
count++;
}
return count – 1;
}
public int Count
{
get
{
return(count);
}
}
void CheckIndex(int index)
{
if (index < 0 || index > count – 1)
throw(new IndexOutOfRangeException(String.Format(“Index {0} out of range”, index)));
}
public int this[int index]
{
get
{
CheckIndex(index);
return(elements[index]);
}
set
{
CheckIndex(index);
elements[index] = value;
}
}
public override string ToString()
{
string[] s = new string[count];
for (int i = 0; i < count; i++)
s[i] = elements[i].ToString();
return(String.Join(“\n”, s));
}
IEnumerator IEnumerable.GetEnumerator()
{
return((IEnumerator) GetEnumerator());
}
public IntegerListEnumerator GetEnumerator()
{
return(new IntegerListEnumerator(this));
}
public class IntegerListEnumerator: IEnumerator
{
IntegerListExplicit list;
int index = -1;
public IntegerListEnumerator(IntegerListExplicit list)
{
this.list = list;
}
public bool MoveNext()
{
index++;
if (index == list.Count)
return(false);
else
return(true);
}
object IEnumerator.Current
{
get
{
return(Current);
}
}
public int Current
{
get
{
return(list[index]);
}
}
public void Reset()
{
index = -1;
}
}
}


 

Comments (5)

  1. BillT says:

    I think those steps 1-4 are a good candidate to be executed by another refactoring menu item on the VS.NET GUI. Right-click on a Enumerator/Enumerable and select "Make strongly typed". I hope MS is going to add a LOT of refactorings to the editor, some in a frequently used area, and the rest in a categorized/searchable area. I also want them to add the refactorings to both C# and VB.NET editors.

  2. Sorry, I should have been more clear. I was specifically referring to the Dispose call that foreach does. The compiler boxes the Enumerator struct to call IDisposable::Dispose.

    Look at the IL for:

    System.Collections.Generic.List<object> l = new System.Collections.Generic.List<object>();

    foreach(object o in l)

    {

    }

    To see what I mean.

  3. I just installed the "Visual Studio 2005 Community Technology Preview March 2004" (who makes up these names?) and I see that it has been fixed. Great!