A while back I wrote about Collection Initializers and Duck Typing, and I wasn't particularly upbeat about the feature. I am not a big fan of duck typing; I believe that class designers should have to declare what abilities their classes have, not leave it up to individual developers (who don't have access to the internals of class) to figure out whether a class has a particular ability or not. And no, just having a method of a particular signature does not mean that a class has a particular capability which happens to require an identical method.
What does this have to do with LINQ? A lot, actually. It turns out that LINQ uses duck typing in a very similar manner to collection initializers. Lets look at an example:
IEnumerable<int> ints = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> evens = from i in ints where i % 2 == 0 select i;
The compiler turns the query expression into the equivalent of this:
ints.Where<int>(i => i % 2 == 0)
Now, "ints" is an IEnumerable<int>, which doesn't define a Where method. Where is actually an extension method, defined in the new System.Linq.Enumerable class. What is interesting about this, is that the compiler doesn't decide directly which Where method to call (i.e. it doesn't bind directly to System.Linq.Enumerable.Where, just because it is expanding a LINQ query): instead, it expands the LINQ query into a Where method call, and then lets normal method call resolution rules take over (its slightly more complicated than that, but conceptually its an accurate description of what happens). This means that the compiler is essentially duck typing that target of the query (in this case "ints"), counting on the standard query operators being defined in some way, either as members or as extension methods. If they are not defined, a compile error is thrown; however, if any members matching the required signatures are defined, they are assumed to be appropriate standard query operators. This is what allows querying over an instance of IQueryable to have a completely different result than querying over as instance of IEnumerable.
Why is duck typing used, instead of using an interface (IQueryable would seem to be appropriate, except that it was co-opted for another purpose)? One could argue that it allows some data sources to implement some of the standard query operators but not all of them, without having to resort to throwing runtime NotSupportedExceptions. This is valid, but could be fixed by defining atomic interfaces instead of a catch-all interface, which is good practice anyway. The real reason why duck typing is used is essentially the same reason that it is used in collection initializers: it is the only way to make the feature work with IEnumerable and IEnumerable<T>, which was clearly a major design goal of the feature. And it is very useful to have; I have found that I can use LINQ queries to perform operations on in-memory sets of data in a much cleaner and clearer way than writing out equivalent algorithms.
So what do I think about this approach? It suffers from the same flawed assumption as all duck typing: it assumes that, if a member (or extension method) is defined which happens to match the signature of a standard query operator, then that method must in fact be a standard query operator. The compiler is essentially assigning semantic meaning to a given method signature, without an associated contract of any kind (i.e. interface). This is a dangerous assumption, as I described in my collection initializers post, and I do not like seeing the increasing use of duck typing in the C# language.
However, my reaction to duck typing in LINQ is not quite as severe as my reaction to collection initializers, for two reasons. First, the standard query operators have fairly unusual signatures, making it far less likely that a method with a matching signature will be defined for a completely different purpose. I would still rather a contract be used, but overall the chances of a collision are very low. Second, LINQ is an enormously useful features, many times more useful than collection initializers. Therefore, I am willing to put up with a little bit of language corruption for the sake of clearer code and greater productivity.
Is there a better way, a way to get this useful functionality without resorting to duck typing? I don't know. I haven't thought of one yet, and I'm sure the C# team put plenty of thought into it before deciding on the approach that they did. Regardless, it is too late now, since any change making the query rules more strict would be definition be a massive breaking change which the C# team would never allow. So I guess this is just one of those things the language purist in me will have to learn to live with.