Consider the following two extension methods:
using System;
using System.Collections.Generic;
using System.Linq;
public static class Extensions
{
public static bool Contains(this IEnumerable self, object obj)
{
foreach (object o in self)
{
if (Object.Equals(o, obj))
{
return true;
}
}
return false;
}
public static bool ContainsEither<T>(this IEnumerable<T> self, T arg1, T arg2)
{
return self.Contains(arg1) || self.Contains(arg2);
}
}
When I wrote my second method, I intended it to call the generic LINQ Enumerable.Contains<T>
method (type arguments inferred from the usage). However, I found out that it is actually calling the first method (my custom Contains()
extension method. When I comment out my Contains()
method, the second method compiles fine, using the Enumerable.Contains<T>()
method.
My question is, why does the compiler choose my Contains()
method with non-generic IEnumerable
argument over Enumerable.Contains<T>()
with IEnumerable<T>
argument? I would expect it to choose Enumerable.Contains<T>()
because IEnumerable<T>
is more derived than IEnumerable
.
Update: Thanks to Jon Skeet for the great answer. It would be interesting to know why the designers chose this way. I spent some time exploring cases where this would matter, I thought of one (granted it's a bit of a reach)
public static double Average(this IEnumerable self)
{
double sum = 0;
long count = 0;
foreach (object obj in self)
{
sum += Convert.ToDouble(obj);
nItems++;
}
return sum / count;
}
object[] junk = new object[] { "9000", (byte)99, -8.555, 3154254325345UL };
double[] clean = new double[] { 9000, 99, -8.555, 3154254325345 };
double junkAvg = junk.Average();
double cleanAvg = clean.Average(); // compiler is choosing your Average() method where you'd prefer it to choose Enumerable.Average(IEnumerable<double>)
My question is, why does the compiler choose my Contains() method with non-generic
IEnumerable
argument overEnumerable.Contains<T>()
withIEnumerable<T>
argument?
Because it's found in the same namespace that contains the method calling it. Types declared within that namespace effectively have precedence over types declared in imported namespaces.
From the C# 5 specification, section 7.6.5.2:
The search for C proceeds as follows:
- Starting with the closest enclosing namespace declaration, continuing with each enclosing namespace declaration, and ending with the containing compilation unit, successive attempts are made to find a candidate set of extension methods:
- If the given namespace or compilation unit directly contains non-generic type declarations Ci with eligible extension methods Mj, then the set of those extension methods is the candidate set.
- If namespaces imported by using namespace directives in the given namespace or compilation unit directly contain non-generic type declarations Ci with eligible extension methods Mj, then the set of those extension methods is the candidate set.
- If no candidate set is found in any enclosing namespace declaration or compilation unit, a compile-time error occurs.
See more on this question at Stackoverflow