调用了哪个扩展方法

Which Extension Method is called

我看到了像 DBSet 这样的类型,它可以同时实现 IQueryable 和 IEnumerable,如下所示:

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

而且很明显IQueryable继承了IEnumerable,并且在System.Linq命名空间中,Enumerable static class和IQueryable static class都定义了一些扩展方法运算符,如First(),Select() 对于 IQueryable 和 IEnumerable,

我想知道像

这样的调用
DBSet<Student> studs = dbContext.Students;
var stu = studs.First();

显然调用了 IQueryable static class 中的 public static TSource First<TSource>(this IQueryable<TSource> source); 方法。

根据 C# 规范 (7.6.5.2),它说:

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.

但在我的例子中,Enumerable 和 IQueryable class 都在 System.Linq 命名空间中,我想知道在这种情况下 public static T First<T>(this IQueryable<T> source) 是如何被决定和调用的?

在这种情况下,将应用正常的方法重载决策。这也在您发布的规范的同一部分中说明:

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.

总结重载结果的结果,这意味着在您的情况下,

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

更受欢迎
public static TFirst<T>(this IEnumerable<T> source) {

因为 IQuerable<T> 继承自 IEnumerable<T>

如果不这样做,会在编译时导致歧义错误。

First 是一个 extension method,我们在 IEnumerableIQueryable 中都有。

所以,问题是当我们实现所有这些接口时,哪一个会被调用?

答案是:来自最派生接口的扩展方法,在本例中 IQueryable 是继承自 IEnumerable 的最派生接口。所以它的 First 扩展方法将被调用。

万物平等。您的问题归结为标准 过载解决方案 规则。

注意 : 阅读语言规范很重要,不要结束得太早或读得太远。在推广时阅读任何相关的子主题也很重要。

既然你在规范中,让我们一起来了解一下吧。

给定

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

public interface IQueryable : IEnumerable

扩展方法

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

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

你的例子

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

您应该引用的规范部分是

强调我的

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).

其中包含一般标准重载解析领域。

强调我的

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.

这导致我们

强调我的

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


这个的简称是

  1. DbSet 实际上实现了 IQueryable 而不是 IEnumerable
  2. 扩展方法在同一个命名空间中
  3. 标准重载决议原则适用于这种情况
  4. 它构建了适用函数成员的候选列表
  5. 应用更好的函数成员原则。

这使得 IQueryable 方法(具有确切类型)更好地匹配


你可以自己测试一下

给定

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 { }

用法

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

结果为 BobExtensions.Test1 将是最佳匹配和所选方法