【问题标题】:Generics: Why can't the compiler infer the type arguments in this case?泛型:为什么编译器在这种情况下不能推断类型参数?
【发布时间】:2011-04-27 11:57:41
【问题描述】:

我想编写一个扩展方法,该方法适用于其值是某种序列的字典。不幸的是,编译器似乎无法从我对该方法的使用中推断出通用参数。我需要明确指定它们。

public static void SomeMethod<TKey, TUnderlyingValue, TValue>
    (this IDictionary<TKey, TValue> dict)
    where TValue : IEnumerable<TUnderlyingValue> { }    

static void Usage()
{
    var dict = new Dictionary<int, string[]>();
    var dict2 = new Dictionary<int, IEnumerable<string>>();

    //These don't compile
    dict.SomeMethod();
    SomeMethod(dict); // doesn't have anything to do with extension-methods
    dict2.SomeMethod(); // hoped this would be easier to infer but no joy


    //These work fine
    dict.SomeMethod<int, string, string[]>();
    dict2.SomeMethod<int, string, IEnumerable<string>>();
}

我意识到类型推断不是一门精确的科学,但我想知道这里是否缺少一些基本的“规则”——我不熟悉规范的细节。

  1. 这是推理过程的一个缺点,还是我期望编译器在这种情况下“弄清楚”是不合理的(可能是模棱两可)?
  2. 我能否更改方法的签名,使其具有同等功能但“可推断”?

【问题讨论】:

  • 推断 TUnderlyingValue 可能很困难。特别是因为IEnumerable&lt;&gt; 的类型参数在.net 4 中是协变的。
  • 类型推断是一门具有形式算法的精确科学,它不仅仅是一种猜测。如果编译器无法推断出类型,那只是因为它没有特定的规则来处理某些情况,而且通常是实现设计,而不是限制。看这里:classes.cs.uchicago.edu/archive/2005/winter/33600-1/slides/…
  • @Jack:谢谢你的链接;那很有意思。请参阅我对 Eric Lippert 的回答的评论,了解我的意思。我想我用词不当。

标签: c# generics type-inference


【解决方案1】:

更新:这个答案是十年前写的;从那时起,类型推断规范和实现已经更新了几次,包括在推断期间如何使用约束的更改。这个答案应该被视为仅具有历史意义;请查阅 C# 规范的最新副本,了解类型推断在当前实现中的工作原理。


我意识到类型推断不是一门精确的科学

我不确定我是否同意。规范很详细。

我想知道这里是否缺少一些基本的“规则”

您缺少的基本规则可能是约束不是签名的一部分。类型推断依赖于签名。

在我看来,这个设计决定有充分的理由。然而,许多人认为我在道德上是错误的,因为我认为这个设计决定有充分的理由。如果您有兴趣阅读关于我是对还是错的几百万字的文章,请参阅我关于该主题的文章以及数百名左右的 cmets 告诉我我错了:

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/constraints-are-not-part-of-the-signature

这是推理过程的缺点吗?

可以说,是的。在我看来,考虑到相互竞争的设计要求,这是一个合理的选择。 (那些是“按照用户的意思去做”和“当事情看起来模棱两可时给出错误”。)

在这种情况下,我期望编译器“弄清楚”是不合理的吗?

没有。你看起来是个通情达理的人,你的期望似乎是基于良好的推理。然而,完全有可能有一个合理的期望却没有得到满足。这就是其中一种情况。

我能否更改方法的签名以使其具有同等功能但“可推断”?

这会很困难,因为通用 Dictionary 类型在其转换中不是协变或逆变的。您想要捕捉的概念在类型系统中不容易以提供推理的方式表达。

如果您更喜欢使用具有更高级类型推断的语言,请考虑使用 F#。如果您更喜欢倾向于“按照用户的意思去做”而不是“报告歧义错误”的语言,请考虑使用 VB。

【讨论】:

  • 太棒了,谢谢 - “约束不是签名的一部分”是我所缺少的,我必须解决这个问题。另一方面,我所说的“不是一门精确的科学”的意思是,两种不同但有效的推理算法(通常,不是特定于规范)可能以不同的方式推断类型参数,但两者在它们的选择。你同意吗?
  • @Ani:当然,有许多不同的可能类型推断器。例如,我们有意选择不在 C# 中使用 Hindley-Milner 风格的类型推断。
  • 你们使用的类型推断的名称是什么?只是想知道它是否是由其他人发明的,比如 Hindley-Milner 风格的类型推断(我假设)。
  • @Joan:我们没有它的名字。这是整个 C# 设计委员会的共同努力。尤其是 Anders、Mads 和我在这方面投入了大量精力。 Erik Meijer 和微软研究团队的一些成员也贡献了许多有用的想法,特别是在如何解决循环情况方面。我不记得是谁在专利声明上。
  • 埃里克,我不知道你是否会注意到这个新评论,但我想问你一些关于你提供的链接的辩论。我觉得人们没有问的一件事 - 为什么“CLR 和 C# 都不认为约束是签名的一部分”?为什么不将其添加到规范中?你有一个链接来给出这方面的立场吗?谢谢。
【解决方案2】:

C# 类型推断不适用于约束或返回值。所以你会有稍微更好的运气

public static void SomeMethod<TKey, TUnderlyingValue>
    (this IDictionary<TKey, IEnumerable<TUnderlyingValue>> dict)
  { }

如果您将参数声明为new Dictionary&lt; string, IEnumerable&lt;int&gt;&gt;(),这将起作用,但如果您将其声明为new Dictionary&lt;string, List&lt;int&gt;&gt;()不会

我不得不说,按照我阅读c# spec 的第7.5.2 节的方式,似乎由于List&lt;int&gt; 实现了IEnumerable&lt;int&gt;,所以TUnderlyingValue 的类型推断应该可以工作。但是,该部分并不完全易于理解。我认为它不能通过多个“层”工作,因为SomeMethod&lt;T&gt;(IEnumberable&lt;T&gt; val){} 可以很好地使用SomeMethod(new List&lt;string&gt;()) 调用它。我在规范中没有具体看到任何涉及解析U = Ca&lt;Va, Cb&lt;Vb&gt;&gt; 的类型,因此可能没有定义该级别的推断。

【讨论】:

  • +1 @Philip Rieck:谢谢。我知道这种解决方法,但它不能解决一般情况,这是我感兴趣的。
  • @Ani 查看编辑 - 我不确定在 c# 4.0 中定义和实现的类型推断是否完全支持该级别的推断。
  • 这不适用于 Dictionary> 因为 IDictionary 是一个不变的类型。假设方法体包含:dict.Add(new ObservableCollection&lt;int&gt;());。如果有某种类型的 IReadOnlyDictionary 接口,它可以在 value 参数上进行协变,并允许两个字典都工作。
【解决方案3】:

为什么不省略 IEnumerable 的类型?

public static void SomeMethod<TKey, TValue>
(this IDictionary<TKey, TValue> dict)
where TValue : IEnumerable { }    

【讨论】:

  • 因为那样你将无法(缺少反射)访问基础值的类型 - 你所知道的是每个 Value 都是 something的序列>,但不是那个东西。
  • 您可以将 TValue 转换为 IEnumerable 因为您知道它的类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-06
  • 1970-01-01
  • 1970-01-01
  • 2012-01-15
相关资源
最近更新 更多