How does method overload resolution work (LINQ Where extension method)?
If I have a variable of type IQueryable<T>
I have four extension methods for Where
in namespace Systm.Linq
available:
public static IQueryable<T> Where<T>(this IQueryable<T> source,
Expression<Func<T, bool>> predicate);
public static IQueryable<T> Where<T>(this IQueryable<T> source,
Expression<Func<T, int, bool>> predicate);
public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
Func<T, bool> predicate);
public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
Func<T, int, bool> predicate);
(The last two because IQueryable<T>
inherits from IEnumerable<T>
.)
If I use a variable of type ObjectQuery<T>
(in namespace System.Data.Objects
) I have five overloads of Where
available, namely the four above (because ObjectQuery<T>
implements IQueryable<T>
and IEnumerable<T>
among other interfaces) and in addition an instance method of this class:
public ObjectQuery<T> Where(string predicate,
params ObjectParameter[] parameters);
If I do the same programming mistake while using either IQueryable<T>
or ObjectQuery<T>
I get very different compiler errors. Here is an example program (standard C# console application template in VS2010 SP1 + System.Data.Entity.dll
assembly added to project references, the compiler error is in comment below the four examples):
using System.Data.Objects;
using System.Linq;
namespace OverloadTest
{
public class Test
{
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
IQueryable<Test> queryable = null;
ObjectQuery<Test> objectQuery = null;
var query1 = queryable.Where(t => t.Name == "XYZ");
// no definition for "Name" in class OverloadTest.Test
var query2 = queryable.Where(t => bla == blabla);
// "bla", "blabla" do not exist in current context
var query3 = objectQuery.Where(t => t.Name == "XYZ");
// Delegate System.Func<Overload.Test,int,bool>
// does not take 1 argument
var query4 = objectQuery.Where(t => bla == blabla);
// Delegate System.Func<Overload.Test,int,bool>
// does not take 1 argument
}
}
}
"Squiggles" look different as well in the compiler:
I understand the first two errors. But why does the compiler apparently want to use the overload number 4 (with the Func<T, int, bool> predicate
) in the last two examples and doesn't tell me that "Name" isn't defined in class Test
and that "bla" and "blabla" do not exist in the current context?
I had expected that the compiler can safely rule out overload number 5 (I don't pass in a string
as parameter) and overload number 2 and 4 (I don't pass in a lambda expression with two parameters (t,i) => ...
) but my expectation doesn't seem to be correct.
As a side note: I came across this problem when looking at this question. The questioner said there that the fourth query in the question does not compile (it has exactly the compiler error in example number 3 and 4 above), but this query is exactly the solution to his problem and to me it seems that something (a variable or property name?) is written wrong in the query (he didn't confirm this though) but this compiler error doesn't give a helpful indication what is wrong.
Edit
Refering to Martin Harris' very helpful comment below:
In example query4
the error "Delegate System.Func does not take 1 argument" is the error shown in the tooltip window when I hover over the squiggle line. In the compiler output window there are actually four errors in this order:
But why doesn't the compiler complain with the first error for the first two examples that use IQueryable<T>
?
Please read until end.
Actually it is because your code has compiler time errors.
Compiler detects correct extension method by looking at your code. In this case it is supposed to take a Test
parameter and return bool
parameter. Since your linq expression cannot be compiled, correct extension method cannot be detected, and compiler assumes first extension method it found is the one you wanted.
BTW, if you fix error like
var query3 = objectQuery.Where(t => t.Id == 1)
compiler will use
public static IQueryable<T> Where<T>(
this IQueryable<T> source,
Expression<Func<T, bool>> predicate
);
Now you should wonder why it skips method on Enumerable. It is because ObjectQuery<T>
class directly implements 'IQueryable', but implements IEnumerable<T>
because of IQueryable<T>
.
you can see object hierarchy below