IcyBrk
IcyBrk

Reputation: 1270

Which Extension Method is called

I saw a type like DBSet, it may implement both IQueryable and IEnumerable like below:

public class DbSet<TEntity> : DbQuery<TEntity>, IDbSet<TEntity>, IQueryable<TEntity>, IEnumerable<TEntity>, IEnumerable, IQueryable, ...

And obviously IQueryable inherits IEnumerable, And in System.Linq namespace, both Enumerable static class and IQueryable static class defined some extension method operators like First(), Select() for both IQueryable and IEnumerable,

I wonder in some invocation like

DBSet<Student> studs = dbContext.Students;
var stu = studs.First();

It is obviously that public static TSource First<TSource>(this IQueryable<TSource> source); method in IQueryable static class is get called.

From C# specification(7.6.5.2), it said:

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.

But in my case, Enumerable and IQueryable class both in System.Linq namespace, I wonder in this situation how is public static T First<T>(this IQueryable<T> source) is being decided and called ?

Upvotes: 5

Views: 161

Answers (3)

adjan
adjan

Reputation: 13652

In that case, normal method overload resolution is applied. This is also stated in the same section of the spec that you have posted:

Otherwise, overload resolution is applied to the candidate set as described in (§7.5.3). If no single best method is found, a compile-time error occurs.

To summarize the result of the overload resolution, this means that in your case,

public static T First<T>(this IQueryable<T> source)

is favored over

public static T First<T>(this IEnumerable<T> source) {

since IQueryable<T> inherits from IEnumerable<T>

If it wouldn't, it would cause an ambiguity error during compile time.

Upvotes: 0

hsn-mnj
hsn-mnj

Reputation: 1398

The First is an extension method, which we have in IEnumerable and in IQueryable.

So, the question is which one is going to get call when we implement all these interfaces?

The answer is: the Extension method from the most derived interface, which in this case IQueryable is the most derived interface which inherits from IEnumerable. So it's First extension method is going to get called.

Upvotes: 3

TheGeneral
TheGeneral

Reputation: 81513

All things being being equal. Your question boils down to standard overload resolution rules.

Note : It's important when reading language specifications, that you don't end too soon or read too far. It's also important to read any relevant sub topics when promoted.

Since you are in the specs, let's work our way through them.

Given

public abstract class DbSet<TEntity> : IQueryable<TEntity>, IAsyncEnumerable<TEntity>, IInfrastructure<IServiceProvider>, IListSource

public interface IQueryable : IEnumerable

Extension methods

namespace System.Linq
{
    public static TSource First<TSource>(this IQueryable<TSource> source) 
    ...
}

namespace System.Linq
{
    public static TSource First<TSource>(this IEnumerable<TSource> source)
    ...
}

Your example

DBSet<Student> studs = dbContext.Students;
var stu = studs.First();

The part of the specs you should be quoting is

Emphasis mine

7.6.5.2 Extension method invocations

...

  • 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.
  • Otherwise, overload resolution is applied to the candidate set as described in (§7.5.3).

Which takes into general standard overload resolution territory.

Emphasis mine

7.5.3 Overload resolution

Overload resolution is a binding-time mechanism for selecting the best function member to invoke given an argument list and a set of candidate function members. Overload resolution selects the function member to invoke in the following distinct contexts within C#:

...

  • Given the set of applicable candidate function members, the best function member in that set is located. If the set contains only one function member, then that function member is the best function member. Otherwise, the best function member is the one function member that is better than all other function members with respect to the given argument list, provided that each function member is compared to all other function members using the rules in §7.5.3.2. If there is not exactly one function member that is better than all other function members, then the function member invocation is ambiguous and a binding-time error occurs.

Which leads us to

Emphasis mine

7.5.3.2 Better function member

For the purposes of determining the better function member, a stripped-down argument list A is constructed containing just the argument expressions themselves in the order they appear in the original argument list.

...

• Otherwise, if MP has more specific parameter types than MQ, then MP is better than MQ


The short of this is

  1. DbSet actually implements IQueryable not IEnumerable
  2. The extension methods are in the same namespace
  3. Standard overload resolution principles are applied in this case
  4. Which constructs a candidate list of applicable function members.
  5. The better function member principles are applied.

This makes the IQueryable method (with the exact type) the better match


You can test it for your self

Given

public static class LobExtensions
{
   public static void Test1(this ILob asd) { }
}

public static class BobExtensions
{
   public static void Test1(this IBob asd) { }
}

public interface ILob { }

public interface IBob : ILob { }

public class Bob : IBob { }

Usage

var asd = new Bob();
asd.Test1();

Result being BobExtensions.Test1 will be the best match and the chosen method

Upvotes: 2

Related Questions