我刚刚遇到这个(编写代码来演示“问题”):
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还解释了如果编译器试图从常见祖先中推断出类型,您可能遇到的惊喜。
答案 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)
在确定右侧的类型时,未考虑左侧 。
只有当编译器独立确定右侧的类型时,它才会检查与左侧的分配兼容性。
至于您声称这两种类型“都有一个共同的祖先”:那将是ICollection
,IEnumerable
,ICollection<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都转换为Z,但这没有帮助。在语言中,一般原则是编译器在应用规则时甚至不查看目标变量。简单示例:double a = 7 / 2; // a becomes 3.0
所以在阅读完这篇文章之后,只需将 1 的结果投射到ICollection<string>
即可。我没有测试过。