Foreach is Duck Typed!
I thought I know how the foreach construct worked under the covers. I figured the compiler would check if the type being iterated over implement IEnumerable or IEnumerator. And if so it will call MoveNext and Current to loop over the elements. But then I read this:
https://blogs.msdn.com/kcwalina/archive/2007/07/18/DuckNotation.aspx
In that post Krzysztof reveals that in reality implementation of those interfaces are NOT checked. In fact, it uses duck typing to determine if iteration can occur. The compiler will just look for a method that matches a certain signature and use that.
Very interesting!
Comments
Anonymous
July 22, 2008
As are the collection initializers in C# 3. I wonder how interesting it'd be to have this as a strongly typed language feature. "Implicit interfaces" or something.Anonymous
July 22, 2008
Are collection initializers using duck typing? How is it being done in this for example: List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; I don't see the need.Anonymous
July 22, 2008
How does it add the items between the braces? The requirement on the type is that it must implement IEnumerable and have an Add method taking the right parameters. That's it as far as I know. Here's a messed up example: class Duckling<T> : IEnumerable<DateTime> { public ArgumentException Add(int x) { Console.WriteLine("Quack " + x); return null; } public IEnumerator<DateTime> GetEnumerator() { throw new NotImplementedException(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw new NotImplementedException(); } } class Program { static void Main(string[] args) { var d = new Duckling<string> { 1, 2, 3 }; } } And it prints : Quack 1 Quack 2 Quack 3Anonymous
July 22, 2008
Wow. Yeah, I see exactly what you mean now. I never though about that. I just tried it out. It really is doing duck typing. I wonder why they chose doing it this way as apposed to a cast check to a new interface.Anonymous
July 22, 2008
Well a new interface isn't appropriate because C# 3 works fine on .NET 2.0 BCL. I'm guessing that requiring a framework upgrade for some pure-language features wasn't acceptable. Also, it's quite flexible. In addition to ignoring the return type of Add, it can take multiple parameters (such as IDictionary). This can be a VERY useful little feature for storing typechecked data. More puzzling is that it requires IEnumerable to be present, though unused. This seems to contradict the idea from foreach. I don't know how many parameters it can work with, but I got to 10 until I got bored. (It also can use params in the Add method.) The thing that annoys me most is that C# implements this in the compiler instead of in the language. The same sentiment goes for query comprehensions. Just for fun: class IWishIHadDuckFeet : System.Collections.IEnumerable { System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return null; } public void Add(int x, string y, DateTime z, Guid a, long c, decimal m, float f, double d, short s, byte b) { } } static class Program { static void Main() { var q = new IWishIHadDuckFeet { { 1, "x", DateTime.Now, Guid.Empty, 1L, 2M, 4.2f, 3.1, 2, 1 } }; }