How does the compiler know what datatype the lambda expression should be

c# ef-core-3.0 entity-framework-core lambda

Question

So when using EF Core and you use most of the Linq extensions you actually use System.Linq.Expressions instead of the usual Func.

So lets say you are using FirstOrDefault on a DbSet.

DbContext.Foos.FirstOrDefault(x=> x.Bar == true);

When you ctrl + lmb on FirstOrDefault it will show you the following overload:

public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

But there is also an overload for Func:

public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)

When you want to store an expression in a variable you can do something like the following:

Func<Entity, bool> = x => x.Bar == true;

and

Expression<Func<Entity, bool>> = x => x.Bar == true;

So how does the compiler decide which overload should be used while using these extension methods?

1
1
10/2/2019 10:35:53 AM

Accepted Answer

Inherited class proximity matters more than exact method parameter types

Notice that the Expression<Func<T,bool>> variant applies to IQueryable<T>, whereas the Func<T, bool> variant applies to IEnumerable<T>.
When looking for a matching method, the compiler will always pick the one closest to the object's type. The inheritance hierarchy is as follows:

DbSet<T> : IQueryable<T> : IEnumerable<T>

Note: there may be other inheritances inbetween, but that doesn't matter. What matters is which is closest to DbSet<T>. IQueryable<T> is closer related to DbSet<T> than IEnumerable<T>.

Therefore, the compiler will try to find a matching method in IQueryable<T>. It asks two questions:

  • Does this type have a method by that name?
  • Do the method parameter types match/map?

IQueryable<T> has a FirstOrDefault method, so bullet point 1 is satisfied); and since x => x.MyBoolean can be implicitly converted to an Expression<Func<T, bool>>, bullet point 2 is also satisfied.

Therefore, you end up with the Expression<Func<T,bool>> variant defined on IQueryable<T>.

Suppose x => x.MyBoolean could not be implicitly converted to Expression<Func<T,bool>> but could be converted to Func<T,bool> (note: this isn't the case, but this could happen for other types/values), then bullet point 2 would not have been satisfied.
At this point, since the compiler did not find a match in IQueryable<T>, it will keep looking further, stumbling on IEnumerable<T> and ask itself the same questions (bullet points). Both bullet points would have been satified.

Therefore, in this case, you would've ended up with the Func<T,bool> variant defined on IEnumerable<T>.

Update

Here's a dotnetfiddle example.

Notice that even though I pass int values (which the base method signature uses), The double signature of the Derived class fits (because int implicitly converts to double) and the compiler never looks in the Base class.

However, this isn't true in Derived2. Since int does not implicitly convert to string, there is no match found in Derived2, and the compiler looks further in Base and uses the int method from Base.

3
10/3/2019 8:47:37 AM

Popular Answer

The accepted answer is a reasonable explanation, but I thought I might provide a little more detail.

So lets say you are using FirstOrDefault on a DbSet. DbContext.Foos.FirstOrDefault(x=> x.Bar == true);

First off, I hope you would not write that. If you want to ask "is it raining?" do you ask "is it raining?" or do you ask "is the statement that it is raining a true statement?" Just say FirstOrDefault(x => x.Bar).

Next, given these overloads:

public static TSource FirstOrDefault<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, bool>> predicate)

public static TSource FirstOrDefault<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate)

How does the compiler choose which overload is the best?

First we do type inference to determine what TSource is in each. The details of the type inference algorithm are complex; ask a more focussed question if you have a question about it.

If type inference fails to determine a type for TSource in either, the failed inference method is discarded from the set of candidates. In your example TSource can be determined to be Foo, presumably.

Next, of the candidates that remain, we check them for applicability of arguments to formals. That is, can we convert every supplied argument to its corresponding formal parameter type? (And of course, is the number of arguments provided correct, and so on.) In your example both methods are applicable.

Of the applicable candidates that remain, we now enter a round of betterness checking. How does betterness checking work? Again, we do it argument-by-argument. In this case we have two questions to answer:

  • DbContext.Foos can be converted to either IEnumerable<Foo> or IQueryable<Foo>. Which, if either, is the better conversion?
  • The lambda can be converted to either a delegate or an expression tree. Which, if either, is the better conversion?

The second question is easy to answer: neither is better. We learn nothing from this argument with respect to betterness.

To answer the first question, we apply the rule conversion to specific is better than conversion to general. If given a choice to convert to Giraffe or Mammal, converting to Giraffe is better. So now the question is which is more specific, IQueryable<Foo> or IEnumerable<Foo>?

The rule of specificity checking is straightforward: if X can be implicitly converted to Y but Y cannot be implicitly converted to X, then X is the more specific. A Giraffe can be used where an Animal is needed, but an Animal cannot be used where a Giraffe is needed, so Giraffe is more specific. Or: every giraffe is an animal, but not every animal is a giraffe, so giraffe is more specific.

By this measure, IQueryable<T> is more specific than IEnumerable<T> because every queryable is an enumerable but not every enumerable is a queryable.

So the queryable is more specific, and therefore that conversion is better.

Now we ask the question "is there a unique applicable candidate method where compared to every other candidate, at least one conversion was better and no conversion was worse?" There is; the queryable candidate has the property that it is better in one argument than every other, and not worse in every other argument, and it is the unique method that has this property.

Therefore overload resolution chooses that method.

I encourage you to read the specification if you have more questions.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow