条件运算符无法隐式转换?

61

我对这个C#小问题感到有些困惑:

给定变量:

Boolean aBoolValue;
Byte aByteValue;
以下代码可成功编译:
if (aBoolValue) 
    aByteValue = 1; 
else 
    aByteValue = 0;

但这样不行:

aByteValue = aBoolValue ? 1 : 0;

错误提示:“无法将类型'int'隐式转换为'byte'。”

当然,这个巨兽会编译通过:

aByteValue = aBoolValue ? (byte)1 : (byte)0;

这里发生了什么?

编辑:

使用 VS2008,C# 3.5。


3个回答

71

这是一个非常常见的问题。

在 C# 中,我们几乎总是从内向外推理。当你看到

x = y;

我们确定x的类型,确定y的类型,以及y的类型是否与x兼容。但是在确定y的类型时,我们不使用我们已知的x的类型。

那是因为可能有不止一个x:

void M(int x) { }
void M(string x) { }
...
M(y); // y is assigned to either int x or string x depending on the type of y
我们需要能够确定表达式的类型,而不知道它被赋值给了什么。类型信息从表达式中流出,而不是流入表达式中。
为了确定条件表达式的类型,我们需要确定结果和替代表达式的类型,并选择两种类型中更普遍的一种作为条件表达式的类型。因此,在您的示例中,条件表达式的类型为"int",并且它不是常量(除非条件表达式是常量true或常量false)。由于它不是常量,所以无法将其分配给byte;当结果不是常量时,编译器仅从类型而不是值推导。
所有这些规则的例外是lambda表达式,其中类型信息会从上下文流入lambda中。正确理解这个逻辑非常困难。

2
我的脑袋都炸了。谢谢您,先生,这让我感到启迪,但也让我感到沮丧。一方面是因为它不能按照看起来应该的方式工作,另一方面是因为它不能按照那种方式工作是有道理的。所以...就用常量吧! - MPelletier
谢谢。我在你的博客上寻找有关这个主题的文章,但这更好。 - John Knoeller
1
@John:我在这里稍微谈到了这些问题:http://blogs.msdn.com/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx - Eric Lippert
2
@Eric Lippert,我试图在您的博客文章上发表评论,但不确定是否成功,所以我会在这里问:为什么编译器不直接将整个三元表达式转换为等效的“冗长表达式”(在本例中为byte aByteValue; if (aBoolValue) aByteValue = 1; else aByteValue = 0;),而是尝试独立解析类型?是否存在某些问题使其不可行?还是出于哲学原因(例如,“如果它看起来像一个表达式,那么它应该像一个表达式一样被评估”)?我认为大多数程序员最初都希望三元运算符的工作方式与if-else完全相同。 - devuxer
1
“var x = y”和“int x = y”不同,应该分别处理。在第一种情况下,我要求编译器自行判断,而在后者中我告诉它我的期望。正如@devuxer所说,大多数开发人员希望“?:”的工作方式类似于“if-else”。 - Thomas Eyde
截至2021年,Eric的文章可以在此找到:https://learn.microsoft.com/en-us/archive/blogs/ericlippert/type-inference-woes-part-one - Andriy K

12

我正在使用 VS 2005,对于bool & Boolean,我可以复现该问题,但对于true则不行。

 bool abool = true;
 Boolean aboolean = true;
 Byte by1 = (abool ? 1 : 2);    //Cannot implicitly convert type 'int' to 'byte'
 Byte by2 = (aboolean ? 1 : 2); //Cannot implicitly convert type 'int' to 'byte'
 Byte by3 = (true ? 1 : 2);     //Warning: unreachable code ;)

最简单的解决方法似乎是进行这种转换

 Byte by1 = (Byte)(aboolean ? 1 : 2);

所以,是的,似乎三元运算符会导致常量“固定”其类型为int,并禁用您从适合较小类型的常量中获得的隐式类型转换。


1
你的解释很有道理。但为什么在这种情况下常量不能固定?而且Mendy的发现让人双倍好奇。微软的某个人应该知道,可能需要强烈争论一下... - MPelletier
我认为Mendy发现当编译器可以轻松检测到它可以完全优化三元运算符时,它会编译等于Byte by = 2的代码,这保留了隐式转换的能力。(请参见上面关于编译器警告的我的评论) - John Knoeller
我认为Eric Lippert可能已经在他的博客上讨论过这个问题的原因。 - John Knoeller
@John Knoeller:你能找到链接吗? - Fitzchak Yitzchaki
这是他博客的相关链接。http://blogs.msdn.com/ericlippert/archive/2009/03/19/representation-and-identity.aspx我没有找到确切的链接,而且我可能也记错了隐式转换的阅读位置。 - John Knoeller
1
@Mendy:找到了。http://blogs.msdn.com/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx - John Knoeller

7

我可能没有一个很好的答案给你,但如果你在许多地方都这样做,你可以声明:

private static readonly Byte valueZero = (byte)0;
private static readonly Byte valueOne = (byte)1;

只需要这些变量。如果它仅限于项目本地,则可以使用const

编辑:使用readonly是没有意义的-这些变量从未被修改。


我相信这只是一个笔误,但你声明了同一个变量两次。 - Cory Charlton
2
@Hamish:我明白你的意思。我可以使用const,但这只是一个解决方法。不过,这绝对不应该让你得到负评。 - MPelletier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接