I have a class which encapsulates a collection of items we'll call ItemCollection
. I need to expose two different enumerators for working with this collection: One simply enumerates all items (Item
) and the second only enumerates those that are a specific derived type (T where T:Item
). A wrinkle is that this part of some real time library code running on the compact framework and it really needs to avoid allocating objects on the heap that could trigger a collection in the middle of the foreach loop. I've been using struct Enumerators to fill that requirement.
public class ItemCollection
{
public ItemEnumerator GetEnumerator() {}
public ItemGenericEnumerator<T> GetEnumerator<T>() {} // Probably not correct
}
public struct ItemEnumerator : IEnumerator<Item>
{
Item Current;
bool MoveNext() {}
void Reset() {}
}
public struct ItemGenericEnumerator<T> : IEnumerator<T> where T : Item
{
T Current;
bool MoveNext() {}
void Reset() {}
}
On the face of it I don't see how to make a specific call to GetEnumerator<T>
and indeed I started with this:
public class ItemCollection
{
public ItemEnumerator GetEnumerator() {}
public ItemGenericEnumerator<T> ItemsOfType<T>() {}
}
foreach(var item in collection.ItemsOfType<X>)
But I immediately ran into does not contain a public definition for 'GetEnumerator'
.
One solution is to have ItemsOfType return a generic throw away class with a constructor that receives the enumerator and a single GetEnumerator method but that implementation breaks the no heap allocation requirement (would a throw away struct that returns another struct with GetEnumerator work?).
Another discarded option is to simply use the first enumerator and require the type to be checked manually, it's only one extra line of code. However the current implimentation details of ItemCollection mean there is a big difference between returning only items of one type compared to pulling all items and then sorting them outside the box.
After your answer, you got me confused for a bit. I guess I had been overthinking your question.
I understand now that the only thing you needed was a struct implementing the GetEnumerator()
method so you could do foreach
, etc. I did not realize that you already had the IEnumerator<>
implementations for the items themselves completed.
A possible improvement would be to have your ItemEnumeratorWrapper
implement the full IEnumerable
interface, so you can make it a private inner struct, as in:
private struct ItemEnumeratorWrapper<T> : IEnumerable<T> where T : Item
{
private ItemCollection _collection;
public ItemEnumeratorWrapper<T>(ItemCollection collection)
{
_collection = collection;
}
public ItemEnumerator<T> GetEnumerator()
{
return _collection.GetItemsOfTypeEnumerator<T>();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((IEnumerable<T>)this).GetEnumerator();
}
}
This allows you to return an IEnumerable<T>
from your ItemsOfType<T>()
method, and hides your implementation.
An alternative to the wrapper, is to have your ItemEnumerator<T>
structs implement both the IEnumerator<T>
and IEnumerable<T>
interfaces, and also make them private. This would add only 2 very small additional methods to their implementation, and remove the need for an intermediate wrapper.
The example below is a bit contrived, because I don't know anything about the internals of your container(s), so I made a few things up to illustrate the idea.
private struct ItemEnumerator<T> : IEnumerator<T>, IEnumerable<T> where T : Item
{
// Specific types for your unsafe internals
// completely made up.
private readonly SomeTypeOfCollectionHandle _handle;
private SomeTypeOfCursor _cursor = SomeTypeOfCursor.BeforeAll;
public ItemEnumerator(SomeTypeOfCollectionHandle handle)
{
_handle = handle;
}
// IEnumerable<T> implementation.
public IEnumerator<T> GetEnumerator()
{
// simply return a new instance with the same collection handle.
return new ItemEnumerator(_handle);
}
public bool MoveNext()
{
return (cursor = _handle.CursorNext(_cursor)).IsValid();
}
public T Current
{
get
{
if (!_cursor.IsValid())
throw new InvalidOperationException();
return _cursor.Read<T>();
}
}
object System.Collections.IEnumerator.Current
{
get { return (object)((IEnumerator<T>)this).Current; }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((IEnumerable<T>)this).GetEnumerator();
}
public void Dispose()
{
_cursor = _handle.CloseCursor(_cursor);
}
}
Doing this would ensure that the public interface of ItemCollection
exposes only standard BCL interfaces.