Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why implement IEnumerable(T) if I can just define ONE GetEnumerator?

Update: I appreciate all of the comments, which have essentially comprised unanimous opposition. While every objection raised was valid, I feel that the ultimate nail in the coffin was Ani's astute observation that, ultimately, even the one miniscule benefit that this idea ostensibly offered -- the elimination of boilerplate code -- was negated by the fact that the idea itself would require its own boilerplate code.

So yeah, consider me convinced: it would be a bad idea.

And just to sort of salvage my dignity somewhat: I might have played it up for argument's sake, but I was never really sold on this idea to begin with -- merely curious to hear what others had to say about it. Honest.


Before you dismiss this question as absurd, I ask you to consider the following:

  1. IEnumerable<T> inherits from* IEnumerable, which means that any type that implements IEnumerable<T> generally must implement both IEnumerable<T>.GetEnumerator and (explicitly) IEnumerable.GetEnumerator. This basically amounts to boilerplate code.
  2. You can foreach over any type that has a GetEnumerator method, as long as that method returns an object of some type with a MoveNext method and a Current property. So if your type defines one method with the signature public IEnumerator<T> GetEnumerator(), it's legal to enumerate over it using foreach.
  3. Clearly, there is a lot of code out there that requires the IEnumerable<T> interface -- for instance, basically all of the LINQ extension methods. Luckily, to go from a type that you can foreach on to an IEnumerable<T> is trivial using the automatic iterator generation that C# supplies via the yield keyword.

So, putting this all together, I had this crazy idea: what if I just define my own interface that looks like this:

public interface IForEachable<T>
{
    IEnumerator<T> GetEnumerator();
}

Then whenever I define a type that I want to be enumerable, I implement this interface instead of IEnumerable<T>, eliminating the need to implement two GetEnumerator methods (one explicit). For example:

class NaturalNumbers : IForEachable<int>
{
   public IEnumerator<int> GetEnumerator()
   {
       int i = 1;
       while (i < int.MaxValue)
       {
           yield return (i++);
       }
   }

   // Notice how I don't have to define a method like
   // IEnumerator IEnumerable.GetEnumerator().
}

Finally, in order to make this type compatible with code that does expect the IEnumerable<T> interface, I can just define an extension method to go from any IForEachable<T> to an IEnumerable<T> like so:

public static class ForEachableExtensions
{
    public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
    {
        foreach (T item in source)
        {
            yield return item;
        }
    }
}

It seems to me that doing this enables me to design types that are usable in every way as implementations of IEnumerable<T>, but without that pesky explicit IEnumerable.GetEnumerator implementation in each one.

For example:

var numbers = new NaturalNumbers();

// I can foreach myself...
foreach (int x in numbers)
{
    if (x > 100)
        break;

    if (x % 2 != 0)
        continue;

    Console.WriteLine(x);
}

// Or I can treat this object as an IEnumerable<T> implementation
// if I want to...
var evenNumbers = from x in numbers.AsEnumerable()
                  where x % 2 == 0
                  select x;

foreach (int x in evenNumbers.TakeWhile(i => i <= 100))
{
    Console.WriteLine(x);
}

What do you guys think of this idea? Am I missing some reason why this would be a mistake?

I realize it probably seems like an overly complex solution to what isn't that big of a deal to start with (I doubt anybody really cares that much about having to explicitly define the IEnumerable interface); but it just popped into my head and I'm not seeing any obvious problems that this approach would pose.

In general, if I can write a moderate amount of code once to save myself the trouble of having to write a small amount of code lots of times, to me, it's worth it.

*Is that the right terminology to use? I'm always hesitant to say one interface "inherits from" another, as that doesn't seem to properly capture the relationship between them. But maybe it's right on.

like image 295
Dan Tao Avatar asked Sep 17 '10 18:09

Dan Tao


People also ask

What is IEnumerable T in C#?

IEnumerable. IEnumerable<T> contains a single method that you must implement when implementing this interface; GetEnumerator, which returns an IEnumerator<T> object. The returned IEnumerator<T> provides the ability to iterate through the collection by exposing a Current property.

What is difference between IEnumerable and IEnumerator in C#?

IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface. This works for readonly access to a collection that implements that IEnumerable can be used with a foreach statement. IEnumerator has two methods MoveNext and Reset. It also has a property called Current.

What is the use of GetEnumerator in C#?

The C# GetEnumerator() method is used to convert string object into char enumerator. It returns instance of CharEnumerator. So, you can iterate string through loop.

Is an array an IEnumerable?

All arrays implement IList, and IEnumerable. You can use the foreach statement to iterate through an array.


1 Answers

You're missing one huge thing -

If you implement your own interface instead of IEnumerable<T>, your class will not work with the framework methods expecting IEnumerable<T> - mainly, you will be completely unable to use LINQ, or use your class to construct a List<T>, or many other useful abstractions.

You can accomplish this, as you mention, via a separate extension method - however, this comes at a cost. By using an extension method to convert to an IEnumerable<T>, you're adding another level of abstraction required in order to use your class (which you'll do FAR more often than authoring the class), and you decrease performance (your extension method will, in effect, generate a new class implementation internally, which is really unnecessary). Most importantly, any other user of your class (or you later) will have to learn a new API that accomplishes nothing - you're making your class more difficult to use by not using standard interfaces, since it violates the user's expectations.

like image 124
Reed Copsey Avatar answered Sep 24 '22 16:09

Reed Copsey