How does method overload resolution work (LINQ Whe

2019-03-27 15:36发布

问题:

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:

  • Delegate System.Func does not take 1 argument
  • "lambda expression" cannot be converted to "string" because "string" is not a delegate type
  • The name "bla" does not exist in the current context
  • The name "blabla" does not exist in the current context

But why doesn't the compiler complain with the first error for the first two examples that use IQueryable<T>?

回答1:

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