【问题标题】:Which Extension Method is called调用哪种扩展方法
【发布时间】:2021-03-04 22:38:09
【问题描述】:

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

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

显然 IQueryable 继承了 IEnumerable,并且在 System.Linq 命名空间中,Enumerable 静态类和 IQueryable 静态类都为 IQueryable 和 IEnumerable 定义了一些扩展方法运算符,如 First()、Select(),

我想知道一些类似的调用

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

显然是调用了IQueryable静态类中的public static TSource First&lt;TSource&gt;(this IQueryable&lt;TSource&gt; source);方法。

来自 C# 规范(7.6.5.2),它说:

如果在给定的命名空间或编译单元中使用命名空间指令导入的命名空间直接包含具有合格扩展方法 Mj 的非泛型类型声明 Ci,那么这些扩展方法的集合就是候选集。

但就我而言,System.Linq 命名空间中的 Enumerable 和 IQueryable 类,我想知道在这种情况下,public static T First&lt;T&gt;(this IQueryable&lt;T&gt; source) 是如何被决定和调用的?

【问题讨论】:

    标签: c# interface linq-to-sql extension-methods


    【解决方案1】:

    一切都是平等的。您的问题归结为标准重载解决规则。

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

    既然你在规范中,让我们一起努力吧。

    给定

    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 扩展方法调用

    ...

    • 从最近的封闭命名空间声明开始,继续每个封闭命名空间声明,并以包含的编译单元结束,连续尝试找到一组候选扩展方法:
      • 如果给定的命名空间或编译单元直接包含具有合格扩展方法 Mj 的非泛型类型声明 Ci,那么这些扩展方法的集合就是候选集。
      • 如果使用给定命名空间或编译单元中的命名空间指令导入的命名空间直接包含具有合格扩展方法 Mj 的非泛型类型声明 Ci,则这些扩展方法的集合就是候选集。
    • 如果在任何封闭的命名空间声明或编译单元中都没有找到候选集,则会发生编译时错误。
    • 否则,将按照 (§7.5.3) 中所述将重载决议应用于候选集。

    这涉及到一般标准重载决议领域。

    强调我的

    7.5.3 过载分辨率

    重载解析是一种绑定时间机制,用于在给定参数列表和一组候选函数成员的情况下选择要调用的最佳函数成员。重载解析选择要在 C# 中的以下不同上下文中调用的函数成员:

    ...

    • 给定一组适用的候选函数成员,最好的 该集合中的函数成员所在的位置。如果集合只包含一个 函数成员,那么那个函数成员就是最好的函数 成员。 否则,最好的函数成员是唯一的函数成员 这比所有其他功能成员更好 给定参数列表,前提是每个函数成员都与 使用 §7.5.3.2 中规则的所有其他函数成员。如果有 不完全是一个函数成员比所有其他函数都好 成员,那么函数成员调用是不明确的,并且 发生绑定时错误。

    这导致我们去

    强调我的

    7.5.3.2 更好的函数成员

    为了确定更好的函数成员,构造了一个精简的参数列表 A,其中仅包含参数表达式本身,按照它们在原始参数列表中出现的顺序。

    ...

    • 否则,如果 MP 比 MQ 有更多特定的参数类型,那么 MP 比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 的结果将是最佳匹配和选择的方法

    【讨论】:

    • 我认为在您摘自 C# Spec 的摘录中,它提到“否则,如果 MP 具有比 MQ 更具体的参数类型,则 MP 比 MQ 更好”,这里应用了“更具体的参数类型”在我的例子中是 IQuerable 是从 IEnumerable 派生的,所以 IQuerable 比 IEnumerable 是“更具体的类型”,我的理解对吗?
    • 你总的解释让我的例子很清楚,谢谢。
    【解决方案2】:

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

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

    答案是:继承自IEnumerable 的最派生接口的扩展方法,在这种情况下IQueryable 是继承自IEnumerable 的最派生接口。所以会调用First 扩展方法。

    【讨论】:

    • 导致与“将军”解释的结果相同,谢谢。
    【解决方案3】:

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

    否则,重载决议将应用于 (§7.5.3) 中所述的候选集。如果没有找到单一的最佳方法,则会发生编译时错误。

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

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

    优于

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

    因为IQuerable&lt;T&gt;继承自IEnumerable&lt;T&gt;

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      • 2015-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多