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

当具有依赖参数的泛型方法推断类型时,在某些情况下会产生意外结果。如果我明确指定类型,则一切正常,而无需任何进一步更改。

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<>接口的协方差。我原本会相反,即通用方法应该推断出参数满足的最特定类型。

问题是:这是错误还是已记录的行为?

参考方案

我原本会相反,即通用方法应该推断出参数满足的最特定类型。

基于什么,究竟是什么?

您看到的行为已记录在案并符合C#规范。您可能会想到,类型推断规范非常复杂。我不会在这里引用全部内容,但是如果您有兴趣的话,可以自己复习。相关部分为7.5.2类型推断。

根据您所写的评论,我认为至少部分原因是您忘记了此方法的三个参数,而不是两个参数(这会影响推理的进行方式)。另外,似乎您希望第二个参数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)

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

因此,对于TSource,标识为List<string>的下限,而对于TKey,标识为IEnumerable<string>的上限(7.5.2.9下限推论)。这些类型就是您提供给调用的内容,因此编译器将使用这些类型。

在第二阶段,尝试修复类型。 TSource不依赖于任何其他参数,因此它首先固定为List<string>。第二阶段的第二次复飞修复了TKey。尽管类型差异允许为TKey设置的边界容纳List<string>,但没有必要,因为根据其边界,可以直接使用您传入的类型。

因此,您将改为使用IEnumerable<string>

当然,如果编译器使用List<string>代替TKey是合法的(如果不符合规范)。如果参数被显式地强制转换,我们可以看到这项工作:

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

这会更改用于调用的表达式的类型,从而更改所使用的界限,并最终更改推理期间选择的实际类型。但是在最初的调用中,即使在允许的情况下,编译器在推理过程中也不需要使用与您指定的类型不同的类型,因此它是不允许的。

C#规范包含一些相当繁琐的部分。类型推断肯定是其中之一,坦率地说,我不是解释规范这一部分的专家。它使我的头部受伤,肯定还有一些我可能不理解的更具挑战性的极端情况(即,我怀疑我是否可以在不做更多研究的情况下实现规范的这一部分)。但我相信以上内容是对与您的问题相关的部分的正确解释,并且希望我已经做了合理的解释。

将List <List <string >>转换为List <string> - c#

This question already has answers here: Closed 9 years ago. Possible Duplicate: Linq: List of lists to a long list我已经使用LINQ进行了转换。List<List<string>>至List<string>.如…

如何在会话状态下添加List <string> - c#

有没有办法在会话中添加列表?或以其他方式在另一个页面中传递List的值? 参考方案 List<string> ast = new List<string>(); ast.Add("asdas!"); Session["stringList"] = ast; List<string> …

为什么要使用Func <string>而不是string? - c#

为什么要使用Func<string>而不是string?我的问题特别是关于this回购。有问题的行是22: private static Func<string> getToken = () => Environment.GetEnvironmentVariable("GitHubToken", Enviro…

无法从ArrayList <String>转换为List <Comparable> - java

当我写下面的代码时,编译器说 无法从ArrayList<String>转换为List<Comparable>private List<Comparable> get(){ return new ArrayList<String>(); } 但是当我用通配符编写返回类型时,代码会编译。private List&l…

Java中的<<或>>>是什么意思? - java

This question already has answers here: Closed 7 years ago. Possible Duplicate: What does >> and >>> mean in Java?我在一些Java代码中遇到了一些陌生的符号,尽管代码可以正确编译和运行,但对于括号在此代码中的作用却感…