Search code examples
c#interfaceextension-methods

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 ?


Solution

  • 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