我是C#新手,我遇到了一个问题。处理三元运算符(? :
)时,C#和Java之间存在差异。
在以下代码段中,为什么第4行不起作用?编译器显示there is no implicit conversion between 'int' and 'string'
的错误消息。第5行不起作用。 List
都是对象,不是吗?
int two = 2;
double six = 6.0;
Write(two > six ? two : six); //param: double
Write(two > six ? two : "6"); //param: not object
Write(two > six ? new List<int>() : new List<string>()); //param: not object
但是,相同的代码适用于Java:
int two = 2;
double six = 6.0;
System.out.println(two > six ? two : six); //param: double
System.out.println(two > six ? two : "6"); //param: Object
System.out.println(two > six ? new ArrayList<Integer>()
: new ArrayList<String>()); //param: Object
缺少C#中的哪种语言功能?如果有,为什么不添加?
答案 0 :(得分:105)
通过 C#5语言规范部分7.14:条件运算符,我们可以看到以下内容:
如果x的类型为X且y的类型为Y,则
如果从X到Y存在隐式转换(第6.1节),而不是从Y到X,那么Y是 条件表达。
如果从Y到X存在隐式转换(第6.1节),而不是从X到Y,则X是 条件表达。
否则,无法确定表达式类型,并发生编译时错误
换句话说:它试图找出x和y是否可以转换为 eachother ,如果没有,则会发生编译错误。在我们的情况下,int
和string
没有显式或隐式转换,因此无法编译。
将此与Java 7 Language Specification section 15.25: Conditional Operator:
进行对比
- 如果第二个和第三个操作数具有相同的类型(可能是空类型),那么这就是条件表达式的类型。 (否强>)
- 如果第二个和第三个操作数之一是原始类型T,而另一个操作数的类型是将装箱转换(第5.1.7节)应用于T的结果,则条件表达式的类型为T.( 否强>)
- 如果第二个和第三个操作数之一是null类型而另一个操作数的类型是引用类型,则条件表达式的类型是该引用类型。 (否强>)
- 否则,如果第二个和第三个操作数的类型可转换(第5.1.8节)为数字类型,则有几种情况:(否)
- 否则,第二和第三操作数分别为S1和S2类型。设T1是将拳击转换应用到S1时产生的类型,让T2为应用到S2的装箱转换产生的类型。
条件表达式的类型是将捕获转换(第5.1.10节)应用于lub(T1,T2)(第15.12.2.7节)的结果。 (是强>)
并且,查看section 15.12.2.7. Inferring Type Arguments Based on Actual Arguments我们可以看到它尝试找到一个共同的祖先,它将作为用于调用Object
的调用的类型。 Object
是可接受的参数,因此调用可以正常工作。
答案 1 :(得分:85)
给出的答案是好的;我想补充一点,这个C#规则是更通用的设计指南的结果。当被要求从几个选项之一推断出表达式的类型时, C#选择其中最独特的。也就是说,如果你给C#一些选择,比如&#34; Giraffe,Mammal,Animal&#34;然后它可能会选择最一般的 - 动物 - 或者它可能会选择最具体的 - 长颈鹿 - 视情况而定。但它必须选择实际给出的一个选择。 C#从未说过#34;我的选择是在Cat和Dog之间,因此我会推断出Animal是最好的选择&#34;。这不是一个选择,所以C#不能选择它。
在三元运算符的情况下,C#尝试选择更通用的int和string类型,但两者都不是更通用的类型。 C#决定不推断任何类型,而不是首先选择一个不是首选的类型,而不是选择类型。
我还注意到这符合C#的另一个设计原则:如果出现问题,请告诉开发人员。语言没有说&#34;我会猜测你的意思,如果我可以&#34;那就糊里糊涂。语言说&#34;我认为你在这里写了一些令人困惑的东西,我会告诉你这个。&#34;
另外,我注意到C#没有从变量到赋值的原因,而是另一个方向。 C#没有说&#34;你正在分配一个对象变量因此表达式必须可以转换为对象,因此我将确保它是&#34;。相反,C#表示&#34;这个表达式必须有一个类型,我必须能够推断出该类型与object&#34;兼容。由于表达式没有类型,因此会产生错误。
答案 2 :(得分:23)
关于泛型部分:
two > six ? new List<int>() : new List<string>()
在C#中,编译器尝试将右侧表达式部分转换为某种常见类型;由于List<int>
和List<string>
是两种不同的构造类型,因此无法将其转换为另一种类型。
在Java中,编译器尝试查找公共超类型而不是转换,因此代码的编译涉及隐式使用wildcards和type erasure;
two > six ? new ArrayList<Integer>() : new ArrayList<String>()
的编译类型为ArrayList<?>
(实际上,它也可以是ArrayList<? extends Serializable>
或ArrayList<? extends Comparable<?>>
,具体取决于使用上下文,因为它们都是常见的泛型超类型)和运行时类型的原始ArrayList
(因为它是常见的原始超类型)。
void test( List<?> list ) {
System.out.println("foo");
}
void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here
// since both test() methods would clash after the erasure
System.out.println("bar");
}
void test() {
test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo
test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo
test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar
} // compiler automagically binds the correct generic QED
答案 3 :(得分:5)
这非常简单。 string和int之间没有隐式转换。三元运算符需要最后两个操作数具有相同的类型。
尝试:
Write(two > six ? two.ToString() : "6");
答案 4 :(得分:5)
在Java和C#(以及大多数其他语言)中,表达式的结果都有一个类型。对于三元运算符,有两个可能的子表达式针对结果进行求值,并且两者必须具有相同的类型。对于Java,可以通过自动装箱将int
变量转换为Integer
。既然Integer
和String
都继承自Object
,它们可以通过简单的缩小转换转换为相同的类型。
另一方面,在C#中,int
是基元,并且没有隐式转换为string
或任何其他object
。