Nullable types and the ternary operator: why is `? 10 : null` forbidden?
我刚遇到一个奇怪的错误:
1 2 3 4
| private bool GetBoolValue()
{
//Do some logic and return true or false
} |
然后,在另一种方法中,像这样:
1
| int? x = GetBoolValue() ? 10 : null; |
简单,如果方法返回true,则将10赋给nullablintx。否则,将null赋给nullable int。但是,编译器会抱怨:
Error 1 Type of conditional expression cannot be determined because there is no implicit conversion between int and .
我疯了吗?
- 请参阅堆栈溢出问题为什么不编译这个C代码?.
- 也许这将在未来的编译器版本中得到纠正,因为"int"和""之间确实存在隐式转换,这就是"int"?
- @布鲁诺,这和说"任何类型和任何其他类型之间确实有一个隐式转换,那就是‘对象’"有什么不同?你认为他们在未来的编译器版本中也会"纠正"这个问题吗?例如:object x=getBoolValue()?(object)"foo":(object)datetime.now;
- 假设编译器足够聪明地说"好,我们没有使用第一个类型(int),那么我们将寻找另一个可以用于表达式结果的类型。"如果您键入"console.writeline((predicate()),您会得到什么?5.6:空).getType().toString());"?你会得到漂浮物吗?还是双份?还是对象?或者一个用户定义的具有从float隐式转换的类?编译器如何知道要选择哪个类型,因为它不能在表达式中选择一个类型?
- 我的意思是,归根结底,对于可以为空的类型没有什么特别之处,除了它们有一个简短的"?"。语法。编译器没有一个特殊的标志告诉它一个int?是"就像一个int加上一点额外的值,所以当你可以的时候尽量使用它。"
- @卢克,我真的不理解你的评论。
- @mquander,它将打印:system.nullable`1[system.single]
- 我也不理解卢克的评论——他引用的代码是有意义的,现在就可以编译了,因为他明确地将每件事都转换成对象。
- @布鲁诺:我的观点是5.6默认情况下是单个的(或者可能是双精度的,我忘记了;假设它是单个的),但是这个表达式不能返回单个的,因为它可以返回空值。因此,编译器必须根据原始类型是单个类型的事实,找出nullable是要使用的正确类型。这听起来很简单,但实际上不是,因为nullable只是编译器的另一个接口,并且没有将其连接到原始类型的特殊C魔力。不知道nullable比nullable甚至object更好。
- 我开始意识到我的困惑…感谢您的所有反馈:)
- @布鲁诺,@mquander,我的观点是要证明,如果你"纠正"编译器,它就会推断出(测试?10:空)应该是int类型?那么,这是否也能让它推断出(测试??foo":datetime.now)的类型应为object,因为两个操作数都可以转换为object。
- @卢克,我理解你的观点,我完全同意。谢谢。
- @布鲁诺,@mquander,我个人更喜欢在我做这种疯狂的事情时看到错误,如果我真的想要一个对象,那么我会做一个显式转换。编译器的当前行为很好:如果两个类型之间有一个隐式转换(不是通过第三个猜测/推断类型),那么一切都可以工作。如果没有隐式转换,那么您将得到一个错误。
- 事实上,整个C中使用的一个微妙但重要的设计原则是,当根据几个备选方案决定什么类型的东西时,我们总是选择唯一的最佳备选方案。我们从不"魔术般地"创造出一种新的类型,它不在我们的选择范围之内,然后选择它。我们只从代码中已经存在的类型中进行选择,并且只有在其中一个类型明显优于其他类型时才进行选择。
编译器首先尝试计算右侧表达式:
1
| GetBoolValue() ? 10 : null |
10是一个int字(不是int?字),null是null字。这两者之间没有隐式转换,因此出现了错误消息。
如果您将右手表达式更改为以下表达式之一,则编译它是因为int?和null(1)之间以及int和int?(2,3)之间存在隐式转换。
1 2 3
| GetBoolValue() ? (int?)10 : null // #1
GetBoolValue() ? 10 : (int?)null // #2
GetBoolValue() ? 10 : default(int?) // #3 |
- 你也可以写new int?()。
- 或者更好的办法。
- 是的,编译器在将int10包装到int?之前需要这个线索。注意,另一种可能是将int10装箱到System.Int32的基类或接口。例如,GetBoolValue() ? (IFormattable)10 : null // #1B或GetBoolValue() ? 10 : (IFormattable)null // #2B就可以了。这种可能性可能是他们不使可以为空的包装自动进行的原因。因为包装和装箱通常都是隐式转换。这是int? variable = 10;和IFormattable variable = 10;都是合法的(前者包装,后者包装)。
- 我希望我的评论符合实际情况。我的要求是在字符串为空时将空值传递给Oracle表编号列。在上面的例子中,我这样管理它:cmd.parameters.add("2",oracledbtype.double).value=string.isNullOrEmpty(pnumberchar)?(双?)空:convert.todouble(pnumberchar);
试试这个:
1
| int? x = GetBoolValue() ? 10 : (int?)null; |
基本上,所发生的是条件运算符无法确定表达式的"返回类型"。由于编译器隐含地决定10是int,因此它决定这个表达式的返回类型也应该是int。由于int不能是null(条件运算符的第三个操作数),它会抱怨。
通过将null强制转换为Nullable,我们明确地告诉编译器这个表达式的返回类型应该是Nullable。你也可以很容易地把10铸造成int?,效果也一样。
- 我知道我可以解决这个问题,我只是好奇为什么会这样?
试试这个:
int? result = condition ? 10 : default(int?);
顺便说一句,C编译器的微软实现实际上以一种非常微妙和有趣的方式(对我来说)使条件运算符的类型分析出错。我的文章是关于类型推理的悲哀,第一部分。
问题是三元运算符是根据第一个参数赋值推断类型的…在本例中是int,而不是可为空的int。
你可能更幸运的是:
1
| int? x = GetBoolValue() (int?)10 : null; |
尝试以下方法之一:
1 2 3
| int? x = GetBoolValue() ? (int?)10 : null;
int? x = GetBoolValue() ? 10 : (int?)null; |
- 我们可以再加一个:int?x=getBoolValue()?10:新的int?();
这是因为编译器通过第二个和第三个操作数来确定条件运算符的类型,而不是通过将结果赋给什么。整数和空引用之间没有直接强制转换,编译器可以使用它来确定类型。
只需添加一个解释演员。
1
| int? x = GetBoolValue() ? 10 : (int?)null; |
三元运算符混淆了,第二个参数是整数,第三个参数也被检查为整数,而NULL不适合。
1
| int? x = GetBoolValue() ? 10 : (int?)null; |
您看到这一点的原因是,在您使用nullable的幕后,您需要告诉C您的"nullable"是nullable的一个空实例。