71

Note This is not a question about how to implement or emulate duck typing in C#...

For several years I was under the impression that certain C# language features were depdendent on data structures defined in the language itself (which always seemed like an odd chicken & egg scenario to me). For example, I was under the impression that the foreach loop was only available to use with types that implemented IEnumerable.

Since then I've come to understand that the C# compiler uses duck typing to determine whether an object can be used in a foreach loop, looking for a GetEnumerator method rather than IEnumerable. This makes a lot of sense as it removes the chicken & egg conundrum.

I'm a little confused as to why this doesn't seem to be the case with the using block and IDisposable. Is there any particular reason the compiler can't use duck typing and look for a Dispose method? What's the reason for this inconsistency?

Perhaps there's something else going on under the hood with IDisposable?

Discussing why you would ever have an object with a Dispose method that didn't implement IDisposable is outside the scope of this question :)

Andreas
  • 5,393
  • 9
  • 44
  • 53
MattDavey
  • 8,897
  • 3
  • 31
  • 54
  • 1
    +1 Interesting. Can you provide a source for the point about `foreach`? – harpo Jun 16 '11 at 08:23
  • 1
    @harpo I just "discovered" what MattDavey did. I am posting some code as an answer below, though it is technically not an answer per se. – gprasant Oct 22 '11 at 04:27
  • Eric Lippert wrote a blog post about this shortly after this question was asked: http://blogs.msdn.com/b/ericlippert/archive/2011/06/30/following-the-pattern.aspx – MattDavey Nov 17 '11 at 09:08
  • Here is good explanation regarding the point about foreach, http://stackoverflow.com/a/398996/1160036. In short, objects don't need to implement `IEnumerable` interface, they just need a `GetEnumerator()` method which returns an object which has `MoveNext()` and `Current`. – Despertar Jul 27 '14 at 06:48
  • @MattDavey that link doesn't work. – David Klempfner Jun 13 '21 at 10:43

3 Answers3

46

There's nothing special about IDisposable here - but there is something special about iterators.

Before C# 2, using this duck type on foreach was the only way you could implement a strongly-typed iterator, and also the only way of iterating over value types without boxing. I suspect that if C# and .NET had had generics to start with, foreach would have required IEnumerable<T> instead, and not had the duck typing.

Now the compiler uses this sort of duck typing in a couple of other places I can think of:

  • Collection initializers look for a suitable Add overload (as well as the type having to implement IEnumerable, just to show that it really is a collection of some kind); this allows for flexible adding of single items, key/value pairs etc
  • LINQ (Select etc) - this is how LINQ achieves its flexibility, allowing the same query expression format against multiple types, without having to change IEnumerable<T> itself
  • The C# 5 await expressions require GetAwaiter to return an awaiter type which has IsCompleted / OnCompleted / GetResult

In both cases this makes it easier to add the feature to existing types and interfaces, where the concept didn't exist earlier on.

Given that IDisposable has been in the framework since the very first version, I don't think there would be any benefit in duck typing the using statement. I know you explicitly tried to discount the reasons for having Dispose without implementing IDisposable from the discussion, but I think it's a crucial point. There need to be good reasons to implement a feature in the language, and I would argue that duck typing is a feature above-and-beyond supporting a known interface. If there's no clear benefit in doing so, it won't end up in the language.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 5
    Expression creation (from lambda) is another example; however, that isn't strictly documented anywhere – Marc Gravell Jun 16 '11 at 08:39
  • 2
    @Jon Skeet Thanks for the answer Jon! Does the compiler use duck typing with collection initializers too? (ie. looking for an Add() method rather than ICollection) – MattDavey Jun 16 '11 at 08:41
  • 5
    The use of duck-typing with "for each" allows the compiler to avoid boxing in cases where the iterator is a struct. While I would consider it bad juju for a struct to implement IEnumerator (generic or not) it can sometimes be advantageous to avoid creating a new object every time loop a loop starts. Having a GetEnumerator method return a struct and an IEnumerable.GetEnumerator method return a class implementing IEnumerator may allow the best of both worlds. – supercat Jun 16 '11 at 15:44
  • 3
    Incidentally, it's worth noting that at least in VB if the return type of GetEnumerator is IEnumerable, the compiler will check *at runtime* to see if it implements IDisposable; if the return type is something else, the compiler will check *at compile time*, but will *not* check at runtime. Even if the enumerator is a derived type that implements IDisposable, if the *declared* type does not implement IDisposable the compiler won't try to call it at runtime. – supercat Jun 16 '11 at 16:01
  • 2
    This can create a problem if a class has an enumerator that does not require IDisposable, but the enumerator of a derived class would require it. Using anything other than IEnumerable<T> as the return type for a GetEnumerator method may prevent derived classes from specifying new enumeration methods. – supercat Jun 16 '11 at 16:04
  • I was concerned that a struct that implements IDisposable could get boxed via a using statement, but it appears (tested in CLR Profiler) that is not the case. Which makes sense, since the compiler knows I'm "using" a struct. (In contrast the compiler cannot know that the IEnumerator returned from IEnumerable.GetEnumerator is a struct -- for example GetEnumerator could conditionally return different types of enumerators.) – yoyo Jun 10 '15 at 16:12
  • @yoyo: The compiler *can* tell that the value returned from the `GetEnumerator` method is a struct if it calls a specific method though - that's why `List` uses explicit interface implementation for `IEnumerable` and `IEnumerable`, but a `foreach` loop over something *known* to be a `List` will call `List.GetEnumerator` which returns a `List.Enumerator` struct. – Jon Skeet Jun 10 '15 at 16:14
  • Yup, you're right. that's what I tried to say with "from IEnumerable.GetEnumerator" -- if the compiler only knows it has an IEnumerable to iterate over then it can't know what type of enumerator it's going to get. – yoyo Jun 10 '15 at 23:15
15

There's no chicken and egg: foreach could depend on IEnumerable since IEnumerable doesn't depend on foreach. The reason foreach is permitted on collections not implementing IEnumerable is probably largely historic:

In C#, it is not strictly necessary for a collection class to inherit from IEnumerable and IEnumerator in order to be compatible with foreach; as long as the class has the required GetEnumerator, MoveNext, Reset, and Current members, it will work with foreach. Omitting the interfaces has the advantage of allowing you to define the return type of Current to be more specific than object, thereby providing type-safety.

Furthermore, not all chicken and egg problems are actually problems: for example a function can call itself (recursion!) or a reference type can contain itself (like a linked list).

So when using came around why would they use something as tricky to specify as duck typing when they can simply say: implement IDisposable? Fundamentally, by using duck typing you're doing an end-run around the type system, which is only useful when the type system is insufficient (or impractical) to address a problem.

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
  • 1
    The chicken & egg scenario I was referring to is that a language feature might depend on a type which is defined in the language itself. eg. IEnumerable depends on C#, and C# (foreach) depends on IEnumerable... – MattDavey Jun 16 '11 at 08:32
  • 1
    Well, in that sense lots of stuff is chicken+eggy: the language feature `+` for integral addition depends on `int` which is defined in the language. It's just part of one big picture :-) – Eamon Nerbonne Jun 16 '11 at 08:41
  • Thinking about it more, it's not the C# language which depends on the interfaces, rather it's the C# compiler, so we have a triangle of dependency :) – MattDavey Jun 16 '11 at 08:46
  • @MattDavey Rather than `+`, a better example would be `delegete`. This is just another name of `System.Delegate`, a normal class that you can call it with its static `Combine` or `Remove` methods. But now you have `+=` and `-=` operators depend on it. – Earth Engine Nov 19 '14 at 02:45
1

The question which you are asking is not a chicken and egg situation. Its more like hows the language compiler is implemented. Like C# and VB.NET compiler are implemented differently.If you write a simple code of hello world and compile it with both the compiler and inspect the IL code they will be different. Coming back to your question, I will like to explain what IL code is generated by C# compiler for IEnumerable.

IEnumerator e = arr.GetEnumerator();

while(e.MoveNext())
{
   e.Currrent;
}

So the C# compiler is tweaked for the case of foreach.

nawfal
  • 70,104
  • 56
  • 326
  • 368
Gaurav Saxena
  • 155
  • 1
  • 6
  • 11 years later I wanted to come back and upvote this answer. I don't know much about language/compiler design but I do know that language/compiler writers love to 'bootstrap' their compiler by writing it in it's own language as soon as possible. What I perceived to be a chicken & egg situation is actually a symbiotic relationship between the language and compiler. – MattDavey Jun 14 '22 at 11:43