C#泛型类型推断与协方差 - 错误或限制 [英] C# generic type inference versus covariance - bug or restriction

查看:114
本文介绍了C#泛型类型推断与协方差 - 错误或限制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当具有相关参数的泛型方法推断出类型时,它在某些情况下会产生意想不到的结果。如果我明确指定了类型,那么所有内容都将无需进行任何更改。

  IEnumerable< List< string>> someStringGroups = null; //仅用于演示
IEqualityComparer< IEnumerable< string>> someSequenceComparer = null;

var groupped = someStringGroups
.GroupBy(x => x,someSequenceComparer);

当然,上面的代码并不打算永远执行,但它证明了结果类型分组是 IEnumerable ,List > 而不是 IEnumerable< ;由于 x =>;因为预期,所以列出< string>,List< string>> x



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

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

如果我不使用显式比较器, b

我认为问题在于,对所提供的参数类型( IEnumerable< string> )采用最小公分母优先超过 IEqualityComparer<> 界面的协方差。我会期待相反的,即一个泛型方法应该推断参数满足的最具体类型。



问题是:这是一个错误或记录的行为?

解决方案


我会期待相反的,即一个泛型方法应该推断参数满足的最具体的类型。


基于,究竟是什么?



您看到的行为已记录并符合C#规范。正如你可能想象的那样,类型推断规范相当复杂。我不会在这里引用整件事情,但如果您有兴趣,您可以自行查阅。相关部分是 7.5.2类型推断



根据您撰写的评论,我认为至少部分困惑源于你忘记了这个方法有三个参数,而不是两个(它影响推理的进行)。另外,看起来你希望第二个参数 keySelector 委托影响类型推断,当它不在这种情况下时(至少不是直接创建它)类型参数之间的依赖关系,但不是以实际的方式)。

但我认为最主要的是您期望类型推断对类型方差更积极在规范中,首先发生的事情在 7.5.2.1之下的规范中描述第一阶段。第二个观点是,对于这个阶段的所有意图和目的,都忽略了。它没有明确声明类型的参数(尽管如此,它并不重要)。在这个阶段,类型推断开始为类型参数开发 bounds ,但不修复参数本身。



GroupBy()

  public static IEnumerable< IGrouping< TKey, TSource>> GroupBy< TSource,TKey>(
这个IEnumerable< TSource>源,
Func< TSource,TKey> keySelector,
IEqualityComparer< TKey>比较器)

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



因此,对于 TSource ,识别 List 的下界,而对于 TKey ,<$ $的上界c $ c> IEnumerable< string> 被标识( 7.5.2.9下界推理)。这些类型是您为调用提供的内容,所以这就是编译器所使用的类型。



在第二阶段,尝试修复这些类型。 TSource 不依赖于任何其他参数,因此它首先被固定为 List< string> 。第二阶段的第二个复飞解决了 TKey 。虽然type 允许为 TKey 设置的界限容纳 List > ,没有必要,因为根据它的界限,你传递的类型可以直接使用。



因此,你最终得到了 IEnumerable< string> / code>代替。



当然,编译器使用是合法的(如果不符合规范)将< string> 列为 TKey 。我们可以看到这个工作,如果参数被明确地转换为相应的:

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

这改变了用于调用的表达式的类型,从而改变了使用的界限,最终当然在推断过程中选择的实际类型。但在最初的调用中,编译器在推断过程中不需要使用与指定的类型不同的类型,即使它已被允许,因此它不会。



C#规范有一些相当多毛病的部分。类型推断绝对是其中之一,坦率地说,我不是解释规范这一部分的专家。这让我头疼,而且肯定会有一些更具挑战性的角落案例,我可能不明白(即,我怀疑我可以实施这部分规范,而没有太多的研究)。但我相信以上是对与您的问题相关的部分的正确解释,我希望我已经做了合理的解释。


When a generic method with dependent parameters infers the type it gives unexpected results in some cases. If I specify the type explicitly everything works without any further Changes.

IEnumerable<List<string>> someStringGroups = null; // just for demonstration
IEqualityComparer<IEnumerable<string>> someSequenceComparer = null;

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

Of course, above code is not intended to be ever executed, but it demonstrates that the result type of grouped is IEnumerable<IEnumerable<string>,List<string>> rather than IEnumerable<List<string>,List<string>> as expected because of x => x.

If I specify the types explicitly everything is fine.

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

If I do not use an explicit comparer everything works as expeceted too.

I think that the problem is that taking the least common denominator of the supplied argument types (IEnumerable<string>) takes precedence over the covariance of the IEqualityComparer<> interface. I would have expected the opposite, i.e. a generic method should infer the most specific type that is satisfied by the arguments.

The question is: is this a bug or a documented behavior?

解决方案

I would have expected the opposite, i.e. a generic method should infer the most specific type that is satisfied by the arguments.

Based on, what, exactly?

The behavior you see is documented and compliant with the C# specification. As you might imagine, the type inference specification is fairly complex. I won't quote the whole thing here, but you can review it yourself if you are interested. The relevant section is 7.5.2 Type inference.

Based on comments you've written, I think at least part of the confusion stems from you forgetting that there are three parameters to this method, not two (which affects how inference proceeds). In addition, it seems you are expecting the second parameter, the keySelector delegate, to affect type inference, when it does not in this case (at least, not directly…it creates a dependency between the type parameters, but not in a material way).

But I think the main thing is that you are expecting type inference to be more aggressive about type variance than the specification in fact requires.

During type inference, the first thing that happens is described in the specification, under 7.5.2.1 The first phase. The second argument is, for all intents and purposes in this phase, ignored. It does not have explicitly declared types for its parameters (though, it wouldn't matter if it did). During this phase, type inference starts to develop bounds for the type parameters, but does not fix the parameters themselves.

You are calling this overload of GroupBy():

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

There are two type parameters that need to be inferred, TSource and TKey. During inference, it is true that the compiler determines upper- and lower-bounds for the type parameters. But these are based on the types passed to the method call. The compiler does not search for alternate base or derived types that would satisfy the type requirements.

So, for TSource, a lower-bound of List<string> is identified, while for TKey, an upper-bound of IEnumerable<string> is identified (7.5.2.9 Lower-bound inferences). These types are what you provided to the call, so that's what the compiler uses.

In the second phase, an attempt to fix the types is made. TSource does not depend on any other parameter, so it's fixed first, as List<string>. A second go-around in the second phase fixes TKey. While type variance allows the bounds set for TKey to accommodate List<string>, there's no need, because according to its bounds the type you passed in can be used directly.

Thus, you wind up with IEnumerable<string> instead.

Of course, it would have been legitimate (if not compliant with the specification) for the compiler to use List<string> as TKey instead. We can see this work if the parameter is explicitly cast accordingly:

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

This changes the type of the expression used for the call, thus the bounds used, and finally of course the actual type chosen during inference. But in the original call, the compiler had no need during inference to use a type different from what you specified, even though it would have been allowed, and so it didn't.

The C# specification has some fairly hairy parts to it. Type inference is definitely one of them, and frankly I am not an expert on interpreting this section of the specification. It makes my head hurt, and there are definitely some more challenging corner cases that I probably don't understand (i.e. I doubt I could implement this part of the specification, without a lot more study). But I believe the above is a correct interpretation of the parts relevant to your question, and I hope that I've done a reasonable job explaining it.

这篇关于C#泛型类型推断与协方差 - 错误或限制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆