【问题标题】:The type of the conditional expression can not be determined? [duplicate]条件表达式的类型不能确定? [复制]
【发布时间】:2011-05-24 09:06:26
【问题描述】:

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

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,我可以理解错误。

我在这里错过了什么?

编辑:

我接受 James Gaunt 的解释。也许只是为了说清楚。我可以很好地阅读编译器规范。我想知道为什么。为什么有人决定以这种方式编写规范。这种设计背后一定有原因。根据詹姆斯的设计原则是“没有惊喜”。 CodeInChaos 还解释了如果编译器尝试从共同祖先推断类型,您可能会遇到什么意外。

【问题讨论】:

  • 尝试在其中一个条件结果上将显式强制转换为ICollection&lt;string&gt;
  • 我知道如何规避它,我只是想了解它。
  • 如果...编译器会创建一个匿名类型,该类型将 (A) 实现所有公共接口 (B) 充当所选对象的包装器?
  • @m0sa 这行不通,因为突然结果指向了另一个实例。
  • 一台机器向你抱怨。我很确定你比那台机器聪明,你知道该怎么做。语言规范中的规则同样是机械的,它阻止你将枪对准你的脚并扣动扳机。它不会炸掉你的脚,它会炸掉你的脸。

标签: c# .net compiler-construction


【解决方案1】:

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

可以添加一个要求,即只有在祖先明确时才会自动转换。但是,添加一个类实现的额外接口突然变成了一个突破性的变化。这可能是不可取的。

例如,假设您让这些类型实现ISerializeable。这不应该改变你的代码的行为,但如果你支持转换为通用接口,它会。

编辑:想多了,发现这个函数已经有同样的问题了:

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

而且这段代码无法编译:

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

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

【讨论】:

  • 现在这是有道理的(尤其是关于后来添加新祖先的部分)。
【解决方案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,但不存在从 X 到 Y 的隐式转换(第 13.1 节),则 X 是 条件表达式。
  • 否则,无法确定表达式类型,并出现编译时错误。

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

因此,在阅读完本文后,我认为只需将 1 的结果投射到 ICollection&lt;string&gt; 就足够了。我没有测试。

【讨论】:

  • 我明白这一点。我想知道为什么。
  • @EricSch,好的,我引用了规则。
  • 我可以阅读规则。再次:为什么?这是一个设计决定,是有原因的。我想知道原因...
【解决方案3】:

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

你可能会说它应该推断出有共同的根类型,但总有一个共同的根类型(例如Object)。

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

这种逻辑贯穿于 C# 的设计中,偶尔会有点烦人,但 更多时候它会阻止你犯错。

【讨论】:

  • var 编译器试图猜测...
  • 不,它不会猜测。 var 只能在 RHS 类型已知的情况下使用。 var 不会让编译器做任何不同的事情——它只是节省你的打字时间。
  • 这种问题经常出现在 Eric Lippert 的博客上。归根结底,这是 C# 的核心设计原则,有时他将其称为“毫不奇怪”。
  • 好的。这就是我想知道的。
【解决方案4】:

在确定右侧的类型时,根本不考虑左侧

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

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

【讨论】:

  • 但是右边的两个表达式仍然有一个共同的祖先(类型方面)。
  • 它们有许多共同的祖先,这使得编译器抛出一个错误......你希望这也能工作吗? (var obj = true ? 1 : "0"),obj 现在是什么类型?根据您的逻辑,它将是 Object 类型,但这很牵强。
  • 好吧,至少编译器可以尝试变得聪明一点。正如客户总是抱怨:“我为什么要那样做,让电脑那样做……”;-)
  • @Eric:如果编译器试图变得聪明,那么在很多情况下它可能会出错。你更喜欢哪一个:一个在一半时间默默产生不正确逻辑的编译器,还是一个行为愚蠢但总是生成正确代码的编译器?
  • 我知道。 F# 编译器对此很聪明,它确实可以正确计算。我不知道 F# 语法是否禁止任何模棱两可的情况...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-01
  • 1970-01-01
相关资源
最近更新 更多