无法确定条件表达式的类型?

时间:2010-12-12 21:45:37

标签: c# .net compiler-construction

我刚刚遇到这个(编写代码来演示“问题”):

public ICollection<string> CreateCollection(int x)
{
    ICollection<string> collection = x == 0 
                                   ? new List<string>() 
                                   : new LinkedList<string>();
    return collection;
}

编译器抱怨:

  

Fehler CS0173:Der Typ des bedingten Ausdrucks kann nicht bestimmt werden,weil keine implizite Konvertierung zwischen“System.Collections.Generic.List”und“System.Collections.Generic.LinkedList”erfolgt。

大致翻译为:

  

无法确定条件运算符的类型,因为List和LinkedList之间没有隐式转换。

我可以看到为什么编译器会抱怨,但是,嘿,来吧。它试图发挥愚蠢。我可以看到两个表达式不是同一类型,而是有一个共同的祖先,作为奖励,左侧的类型也是共同的祖先。我相信编译器也可以看到它。如果左侧被声明为var,我可以理解错误。

我在这里缺少什么?

修改:

我接受詹姆斯·冈特的解释。也许只是为了说清楚。我可以很好地阅读编译器规范。我想了解原因。为什么有人决定以这种方式编写规范。这种设计背后必然有一个原因。根据詹姆斯的说法,设计原则是“毫无意外”。此外,CodeInChaos还解释了如果编译器试图从常见祖先中推断出类型,您可能遇到的惊喜。

4 个答案:

答案 0 :(得分:10)

表达式(a?b:c)必须解析为某种类型。类型将是b或c的类型。如果它们不同(并且没有从一个到另一个的隐式转换),编译器在编译时不知道这是哪种类型。

您可能会说它应该推断出存在共同的根类型,但总有一个共同的根类型(例如对象)。

通常,C#编译器不会尝试猜测你的意思。如果要使用公共根类型,则将b和c强制转换为该类型。

这种逻辑在整个C#的设计中运行,偶尔会有点烦人,但更经常会阻止你犯错误。

答案 1 :(得分:4)

由于接口,它们可以有多个不同的共同祖先。

可以添加一个要求,即如果祖先是明确的,它只会自动转换。但是,然后添加类实现的其他接口突然变成了一个突破性变化。这可能并不可取。

例如,假设您使这些类型实现ISerializeable。这不应该改变你的代码的行为,但是如果你支持那种转换到通用接口那么。

编辑:想一想它,并注意到这个函数已经有完全相同的问题:

T MyFunc<T>(T left,T right)

此代码无法编译:

ICollection<string> r=MyFunc(new List<string>() , new LinkedList<string>());

因为它无法决定使用哪种类型作为类型参数T。因此,?:运算符的行为与重载决策一致。

答案 2 :(得分:2)

在确定右侧的类型时,未考虑左侧

只有当编译器独立确定右侧的类型时,它才会检查与左侧的分配兼容性。

至于您声称这两种类型“都有一个共同的祖先”:那将是ICollectionIEnumerableICollection<T>IEnumerable<T>还是Object?编译器应该使用哪种启发式方法来明确确定想要的类型?编译器只是要求您指定,而不是试图猜测您的意图。

答案 3 :(得分:2)

?:的定义只是要求相同的类型 你当然可以使用

? (ICollection<string>) new List<string>() 
: (ICollection<string>) new LinkedList<string>();

或者只使用if/else

根据C#参考,§14.13,

[Given]形式b ? x : y

的条件表达式
  • 如果X和Y是相同的类型,那么这是条件表达式的类型。
  • 否则,如果从X到Y存在隐式转换(第13.1节),而不是从Y到X,则Y是类型 条件表达式。
  • 否则,如果从Y到X存在隐式转换(第13.1节),而不是从X到Y,则X是类型 条件表达式。
  • 否则,无法确定表达式类型,并发生编译时错误。

在你的情况下,X和Y都转换为Z,但这没有帮助。在语言中,一般原则是编译器在应用规则时甚至不查看目标变量。简单示例:double a = 7 / 2; // a becomes 3.0

所以在阅读完这篇文章之后,只需将 1 的结果投射到ICollection<string>即可。我没有测试过。