UPDATE: This question was the subject of my blog in July 2013. Thanks for the great question!
You have discovered an unfortunate edge case in the generic method type inference algorithm. We have:
Distinct<X>(IEnumerable<X>, IEqualityComparer<X>)
where the interfaces are:
IEnumerable<out T> -- covariant
and
IEqualityComparer<in T> -- contravariant
When we make the inference from allPositions
to IEnumerable<X>
we say "IEnumerable<T>
is covariant in T, so we can accept Position
or any larger type. (A base type is "larger" than a derived type; there are more animals than giraffes in the world.)
When we make the inference from the comparer we say "IEqualityComparer<T>
is contravariant in T, so we can accept BaseClass
or any smaller type."
So what happens when it comes time to actually deduce the type argument? We have two candidates: Position
and BaseClass
. Both satisfy the stated bounds. Position
satisfies the first bound because it is identical to the first bound, and satisfies the second bound because it is smaller than the second bound. BaseClass
satisfies the first bound because it is larger than the first bound, and identical to the second bound.
We have two winners. We need a tie breaker. What do we do in this situation?
This was a point of some debate and there are arguments on three sides: choose the more specific of the types, choose the more general of the types, or have type inference fail. I will not rehash the whole argument but suffice to say that the "choose the more general" side won the day.
(Making matters worse, there is a typo in the spec that says that "choose the more specific" is the right thing to do! This was the result of an editing error during the design process that has never been corrected. The compiler implements "choose the more general". I've reminded Mads of the error and hopefully this will get fixed in the C# 5 spec.)
So there you go. In this situation, type inference chooses the more general type and infers that the call means Distinct<BaseClass>
. Type inference never takes the return type into account, and it certainly does not take what the expression is being assigned to into account, so the fact that it chooses a type that is incompatible with the assigned-to variable is not it's business.
My advice is to explicitly state the type argument in this case.