It’s possible to provide multiple generic enumerators for a single class. The trick is that clients must specify which enumerator to use.
For example, here is a “MyNumbers” class with multiple generic enumerators (in this case, for custom class “MyInt” and integer):
public class MyNumbers : IEnumerable<MyInt>, IEnumerable<int>
To enumerate through the class, you must use a cast to explicitly specify which enumerator to use, as follows:
foreach (MyInt mi in (IEnumerable<MyInt>)numbers) { // do stuff }
Note that if you don’t explicitly specify the enumerator, the compiler will generate the following error:
foreach statement cannot operate on variables of type ‘MyNumbers’ because it implements multiple instantiations of ‘System.Collections.Generic.IEnumerable<T>’, try casting to a specific interface instantiation
Sample Code
Here is a simple console program that demonstrates multiple enumerators. For simplicity, I created a List<int> of integers for IEnumerable<int>.GetEnumerator. A more efficient approach is to create a custom enumerator (topic for a future article).
using System; using System.Collections; using System.Collections.Generic; namespace MultipleEnumeration { class Program { static void Main( string[] args ) { MyNumbers numbers = new MyNumbers(); numbers.Add( new MyInt( 4 ) ); numbers.Add( new MyInt( 8 ) ); numbers.Add( new MyInt( 15 ) ); foreach (MyInt mi in (IEnumerable<MyInt>)numbers) { Console.WriteLine( mi ); } foreach (int i in (IEnumerable<int>)numbers) { Console.WriteLine( i ); } Console.ReadLine(); } } public class MyInt { public MyInt() {} public MyInt( int i ) { this.Value = i; } public int Value; public override string ToString() { return this.Value.ToString(); } } public class MyNumbers : IEnumerable<MyInt>, IEnumerable<int> { private List<MyInt> m_Numbers = new List<MyInt>(); public void Add( MyInt i ) { this.m_Numbers.Add( i ); } IEnumerator<MyInt> IEnumerable<MyInt>.GetEnumerator() { return this.m_Numbers.GetEnumerator(); } IEnumerator<int> IEnumerable<int>.GetEnumerator() { List<int> ints = new List<int>(); for (int i = 0; i < this.m_Numbers.Count; i++) { ints.Add( this.m_Numbers[i].Value ); } return ints.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.m_Numbers.GetEnumerator(); } } }
This is interesting, but I would tend to consider multiple enumerable interfaces as a smell for a poorly designed class. Perhaps it would be better to create two properties returning a dedicated enumerable collection (Like the DataGridView class which has “Rows” and “Columns” properties, instead of being directly able to enumerate rows and columns.)
Although I have not seen any C# coding guidelines that prohibit multiple enumerable interfaces, I would agree that it’s not an intuitive solution if the enumerable data types are incompatible (e.g., enumerating Color and Rectangle). In this case we are enumerating over the same collection but returning different, compatible types (though for simplicity in this example I omitted the implicit conversion between MyInt and int).
In our final solution, we ended up enumerating over just a single type and provided a separate property for the other type, simply to be more clear to the clients. But it was interesting to discover that enumerating over multiple types in the same class is possible.
Thanks for commenting!
[…] Multiple Generic IEnumerable<T> – Tim M […]
Yes. Actually, I didn’t know it was possible. Thanks for the article.
Thank’s a lot…