代码之家  ›  专栏  ›  技术社区  ›  Marcel

C#泛型推断与协方差-错误或限制

  •  3
  • Marcel  · 技术社区  · 7 年前

    IEnumerable<List<string>> someStringGroups = null; // just for demonstration
    IEqualityComparer<IEnumerable<string>> someSequenceComparer = null;
    
    var grouped = someStringGroups
      .GroupBy(x => x, someSequenceComparer);
    

    grouped IEnumerable<IEnumerable<string>,List<string>> 而不是 IEnumerable<List<string>,List<string>> x => x

    如果我明确指定类型,一切都很好。

    var grouped = someStringGroups
      .GroupBy<List<string>,List<string>>(x => x, someSequenceComparer);
    

    如果我不使用显式比较器,那么一切也都能正常工作。

    IEnumerable<string> )需要 优先 IEqualityComparer<>

    错误或记录的行为?

    2 回复  |  直到 7 年前
        1
  •  2
  •   Peter Duniho    7 年前

    基于什么?

    您看到的行为已记录在案,并符合C规范。正如您可能想象的那样,类型推断规范相当复杂。我不会在这里引用全部内容,但如果你感兴趣的话,你可以自己复习。相关章节为 .

    根据你写的评论,我认为至少部分困惑源于你忘记了有 此方法的参数,而不是两个(这会影响推理的进行方式)。此外,您似乎需要第二个参数 keySelector 委托,以影响类型推断,在这种情况下它不会(至少,不会直接…它在类型参数之间创建依赖关系,但不会以实质性方式)。

    但我认为主要的是,您期望类型推断在类型差异方面比规范实际需要的更激进。

    7.5.2.1第一阶段

    GroupBy()

    public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector,
        IEqualityComparer<TKey> comparer)
    

    需要推断两个类型参数, TSource TKey 。在推理过程中,编译器确实确定了类型参数的上限和下限。但这些都是基于传递给方法调用的类型。编译器不会搜索满足类型要求的备用基类型或派生类型。

    t来源 List<string> 已识别,而对于 IEnumerable<string> 已识别( 7.5.2.9下限推断 ). 这些类型是您提供给调用的类型,因此编译器使用这些类型。

    在第二阶段,尝试修复这些类型。 不依赖于任何其他参数,因此它首先是固定的,因为 列表(<);字符串(>); TKey键 TKey键 以适应 ,这是没有必要的,因为根据其边界,您传入的类型可以直接使用。

    因此,你最终会

    当然,编译器使用它是合法的(如果不符合规范) 列表(<);字符串(>); TKey键 相反如果相应地显式转换参数,我们可以看到这项工作:

    var grouped2 = someStringGroups
      .GroupBy(x => x, (IEqualityComparer<List<string>>)someSequenceComparer);
    

    这会更改用于调用的表达式的类型,从而更改所使用的边界,最后当然还会更改在推理过程中选择的实际类型。但是在最初的调用中,编译器在推理过程中不需要使用与您指定的类型不同的类型,即使它本来是允许的,但事实并非如此。

    使生效

        2
  •  0
  •   Chris    7 年前

    我敢肯定这是意料之中的行为。

    我们感兴趣的方法签名是:

    public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector,
        IEqualityComparer<TKey> comparer
    )
    

    source 属于类型 IEnumerable<List<string>> TSource List<string> 这个 comparer IEqualityComparer<IEnumerable<string>> 因此,自然选择 TKey IEnumerable<string>

    如果我们再看最后一个参数 keySelector x=>x 。这是否满足到目前为止的类型约束?是的,因为x是一个 列表(<);字符串(>); 可以隐式转换为 .

    在这一点上,编译器为什么还要寻找更多的东西呢?自然和明显的选择,没有铸造需要的作品,所以它使用。如果你不喜欢它,你总是可以选择做你做的,并明确指出通用参数。

    当然你也可以把比较器做成 IEqualityComparer<List<string>>