views:

195

answers:

6

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.

+1  A: 

It is so much easier to use IEnumerable for reflection. Trying to invoke generic interfaces via reflection is such a pain. The penalty of boxing via IEnumerable is lost by the overhead of reflection itself so why bother using a generic interface? As for an example, serialization comes to mind.

ChaosPandion
Can you elaborate on that? I'm not disputing your answer; I'm just not quite sure what you mean.
Dan Tao
@Dan Tao: I think he means being able to reflect into an IEnumerable and it's members is easier than reflecting into an IEnumerable<T>
Jimmy Hoffa
+10  A: 

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.

Reed Copsey
But that's what the `AsEnumerable` extension method I suggested is meant to address.
Dan Tao
@Dan so then you have to remember to call an extra method every time you want your own version of IEnumerable to be useful?
Rex M
@Dan: I added a second paragraph explaining why that's not as good as just implementing IEnumerable<T>
Reed Copsey
@Rex M: Well, depends on what is useful to you. I suppose if you're always using LINQ, then yeah. If you're typically just planning to use `foreach` yourself, then you wouldn't need to call the extension method.
Dan Tao
This really is the most important point. You really shouldn't fight the framework you are working with.
ChaosPandion
+3  A: 

In our codebase there's probably more than 100 instances of this exact snippet:

IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

And I am really OK with that. It's a tiny price to pay for full compatibility with every other piece of .NET code ever written ;)

Your proposed solution essentially requires every consumer/caller of your new interface to also remember to call a special extension method on it before it is useful.

Rex M
+1, it really is just a one-liner.
Hans Passant
I think you won me over with "I am really OK with that." In the end I guess I need to just accept that having to type out a few more lines hardly constitutes a problem worth fighting.
Dan Tao
+5  A: 

You're right: it does seem an overly complex solution to a pretty easy problem.

It also introduces an extra level of indirection for every step of the iteration. Probably not a performance problem, but still somewhat unnecessary when I don't think you're really gaining anything very significant.

Also, although your extension method lets you convert any IForEachable<T> into an IEnumerable<T>, it means your type itself won't satisfy a generic constraint like this:

public void Foo<T>(T collection) where T : IEnumerable

or the like. Basically, by having to perform your conversion, you're losing the ability to treat a single object as both an implementation of IEnumerable<T> and the real concrete type.

Also, by not implementing IEnumerable, you're counting yourself out of collection initializers. (I sometimes implement IEnumerable explicitly just to opt into collection initialization, throwing an exception from GetEnumerator().)

Oh, and you've also introduced an extra bit of infrastructure which is unfamiliar to everyone else in the world, compared with the vast hordes of C# developers who already know about IEnumerable<T>.

Jon Skeet
Good call on the points about generic constraints and collection initializers. As quickly became obvious to me as the answers rolled in, there are a ton of reasons not to do this! Anyway, it was really helpful to have such an immediate (and **strong**) reaction from some respected SO folks on this one. So thanks!
Dan Tao
What sort of object do you implement `IEnumerable` on so you can use collection initializer syntax *without* actually being able to enumerate it?
Gabe
@Jon, interesting answer, as always. However I don't get your point about collection initializers: don't you need to implement ICollection rather than IEnumerable ?
Thomas Levesque
Actually you can ignore my previous comment: I just looked it up on MSDN, you just need to implement IEnumerable and provide an Add method...
Thomas Levesque
@Gabe: Sometimes being able to iterate over something just isn't useful, or at least not part of the main design. Imagine a benchmark runner, where you want to be able to provide a series of tests and then execute them... from the outside you don't need to be able to enumerate them, one by one. Thinking about your question has sparked off a number of extra ideas though, so watch my blog... hopefully over the weekend I'll find some time to write something up.
Jon Skeet
+2  A: 

Aren't you just moving the boilerplate somewhere else - from writing the IEnumerable.GetEnumeratormethod on each class to calling your AsEnumerable extension every time an IEnumerable<T> is expected? Typically, I would expect an enumerable type to be used for querying far more times than it is written (which is exactly once). This would mean that this pattern will lead to more boilerplate, on average.

Ani
Haha yes, but then it's 1 line instead of 3 lines... OK, so it's not the most worthwhile idea ;)
Dan Tao
It's still only one line if you just write it as `IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }`
Gabe
A: 

I think everyone has already mentioned the technical reasons not to do this, so I'll add this into the mix: requiring your user to call AsEnumerable() on your collection to be able to use enumerable extensions would violate the principle of least surprise.

Jimmy Hoffa