C#和Java的三元运算符之间的区别(?:)

bla*_*234 94 c# java ternary-operator conditional-operator

我是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
Run Code Online (Sandbox Code Playgroud)

但是,相同的代码适用于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
Run Code Online (Sandbox Code Playgroud)

缺少C#中的哪种语言功能?如果有,为什么不添加?

Jer*_*vel 106

通过C#5语言规范部分7.14:条件运算符,我们可以看到以下内容:

  • 如果x的类型为X,则y的类型为Y.

    • 如果从X到Y存在隐式转换(第6.1节),而不是从Y到X,则Y是条件表达式的类型.

    • 如果从Y到X存在隐式转换(第6.1节),而不是从X到Y,则X是条件表达式的类型.

    • 否则,无法确定表达式类型,并发生编译时错误

换句话说:它试图找到x和y是否能够被转换为海誓山盟并且如果不是,则产生编译错误.在我们的例子中int,string没有显式或隐式转换,所以它不会编译.

将其与Java 7语言规范部分15.25对比:条件运算符:

  • 如果第二个和第三个操作数具有相同的类型(可以是null类型),那么这就是条件表达式的类型.()
  • 如果第二个和第三个操作数之一是原始类型T,而另一个操作数的类型是对T应用装箱转换(第5.1.7节)的结果,那么条件表达式的类型是T.(NO)
  • 如果第二个和第三个操作数之一是null类型而另一个操作数的类型是引用类型,则条件表达式的类型是该引用类型.()
  • 否则,如果第二个和第三个操作数具有可转换(第5.1.8节)到数字类型的类型,则有几种情况:( )
  • 否则,第二和第三操作数分别是S1和S2类型.设T1是将拳击转换应用于S1所产生的类型,让T2为应用到S2的装箱转换所产生的类型.
    条件表达式的类型是将捕获转换(第5.1.10节)应用于lub(T1,T2)(第15.12.2.7节)的结果.()

并且,请看第15.12.2.7节.推断类型参数根据实际参数,我们可以看到它试图找到一个共同的祖先,它将作为用于调用它的调用的类型Object.Object 这是一个可接受的参数,因此呼叫将起作用.


Eri*_*ert 86

给出的答案是好的; 我想补充一点,这个C#规则是更通用的设计指南的结果.当被要求从几个选项之一推断出表达式的类型时,C#会选择其中最独特的一个.也就是说,如果你给C#一些选择,比如"长颈鹿,哺乳动物,动物"那么它可能会选择最一般的 - 动物 - 或者它可能会选择最具体的 - 长颈鹿 - 视情况而定.但它必须选择实际给出的选择之一.C#从不说"我的选择是在猫与狗之间,因此我会推断出Animal是最好的选择".这不是一个选择,所以C#不能选择它.

在三元运算符的情况下,C#尝试选择更通用的int和string类型,但两者都不是更通用的类型.C#决定不推断任何类型,而不是选择一个首先不是选择的类型.

我还注意到这符合C#的另一个设计原则:如果出现问题,请告诉开发人员.这种语言并没有说"我会猜到你的意思,如果可以的话,就会糊里糊涂".语言说"我觉得你在这里写了一些令人困惑的东西,我会告诉你这件事."

另外,我注意到C#并不是从变量指定值的原因,而是另一个方向.C#没有说"你正在分配一个对象变量,因此表达式必须可以转换为对象,因此我将确保它是".相反,C#说:"这种表达必须有一个类型,我必须能够推断出该类型与对象兼容".由于表达式没有类型,因此会产生错误.

  • 长颈鹿太棒了! (21认同)
  • @Pharap:严肃地说,为什么我如此使用长颈鹿有一些教学原因.首先,长颈鹿在世界各地都很有名.其次,这是一种有趣的说法,而且看起来很奇怪,这是一个令人难忘的形象.第三,使用比方说"长颈鹿"和"乌龟"强烈强调"这两个类别,尽管它们可能共用一个基类,但是具有非常不同特征的动物非常不同".关于类型系统的问题通常有一些例子,其中类型在逻辑上非常相似,这会以错误的方式驱动你的直觉. (9认同)
  • @Pharap:也就是说,问题通常是"为什么不能将客户发票提供商工厂列表用作发票提供商工厂列表?" 而且你的直觉说:"是的,那些基本上都是一样的",但当你说"为什么不能把充满长颈鹿的房间用作充满哺乳动物的房间?" 然后说"因为一个满是哺乳动物的房间可以容纳一只老虎,一个满是长颈鹿的房间不能",这就更加直截了当了. (7认同)
  • 我超越了第一段,想知道为什么突然出现协方差/逆变的兴趣,然后我开始对第二段开始达成一致,然后我看到谁写了答案......应该早点猜到.你有没有注意到[你用多少'长颈鹿'作为一个例子](https://www.google.co.uk/search?q=Eric+Lippert+Giraffe&ie=utf-8&oe=utf-8&gws_rd=cr&ei= vpO1VuTJCMGra4mqobgN)?你一定非常喜欢长颈鹿. (6认同)
  • @Eric我最初用`int?遇到了这个问题?b =(a!= 0?a:(int?)null)`.还有[这个问题](http://stackoverflow.com/questions/2215745),以及旁边的所有相关问题.如果你继续关注链接,那就有很多.相比之下,我从来没有听说过有人像Java那样遇到现实问题. (6认同)
  • @ BlueRaja-DannyPflughoeft:我同意你的情景既常见又烦人.可能值得为语言添加一个特殊情况,即在推断类型的情况下,如果其中一个表达式为null,则推理中涉及的任何值类型的可空版本将自动成为候选. (5认同)
  • 老实说,我认为这是一个设计错误.它使得ternery操作员烦人,因为我不得不明确地编写编译器可能容易暗示的转换.我想不出这样可以避免开发人员在脚下拍摄自己的情况,这简直令人讨厌. (2认同)

Mat*_*don 24

关于泛型部分:

two > six ? new List<int>() : new List<string>()
Run Code Online (Sandbox Code Playgroud)

在C#中,编译器尝试右侧表达式部分转换为某种常见类型; 由于List<int>List<string>是两个不同的构造类型,一个不能被转换到另一个.

在Java中,编译器尝试查找公共超类型而不是转换,因此代码的编译涉及隐式使用通配符类型擦除 ;

two > six ? new ArrayList<Integer>() : new ArrayList<String>()
Run Code Online (Sandbox Code Playgroud)

具有编译类型ArrayList<?>(实际上,它也可以是,ArrayList<? extends Serializable>或者ArrayList<? extends Comparable<?>>,取决于使用上下文,因为它们都是常见的泛型超类型)和raw的运行时类型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
Run Code Online (Sandbox Code Playgroud)

  • 确切地说@ blackr1234:我不想重复已经说过的其他答案,但是三元表达式需要评估一个类型,编译器不会尝试找到两者的"最小公分母". (2认同)
  • 实际上,这不是关于类型擦除,而是关于通配符类型.`ArrayList <String>`和`ArrayList <Integer>`的删除将是`ArrayList`(原始类型),但这个三元运算符的推断类型是`ArrayList <?>`(通配符类型).更一般地说,Java*runtime*通过类型擦除实现泛型对表达式的*compile time*类型没有影响. (2认同)
  • 实际上,"两个>六个"的类型?new ArrayList <Integer>():new ArrayList <String>()`是`ArrayList <?扩展Serializable&Comparable <?>>`这意味着你可以将它分配给`ArrayList <?类型的变量.扩展Serializable>`以及`ArrayList <?的变量?扩展Comparable <?>>`,但当然是`ArrayList <?>`,它相当于`ArrayList <?extends Object>`,也适用. (2认同)

Cod*_*ice 6

在Java和C#(以及大多数其他语言)中,表达式的结果都有一个类型.对于三元运算符,有两个可能的子表达式针对结果进行求值,并且两者必须具有相同的类型.在Java的情况下,int可以Integer通过自动装箱将变量转换为a .现在既然IntegerString继承Object,它们可以通过简单的缩小转换转换为相同的类型.

另一方面,在C#中,a int是原语,并且没有隐式转换string或任何其他转换object.

  • 这与自动装箱无关,在C#中也会发生这种情况.不同之处在于,C#不会尝试在Java(仅限Java 5)的情况下找到常见的超类型.您可以通过尝试查看如果您使用2个自定义类(它们都明显地从对象继承)尝试会发生什么来轻松地测试它.还有从`int`到`object`的隐式转换. (9认同)
  • 并且更多地挑剔:从"整数/字符串"到"对象"的转换不是一个缩小的转换,它恰恰相反:-) (3认同)
  • 我不认为这是正确的,因为在C#中完全可以将int装入对象.我认为,这里的区别在于,C#在确定是否允许时采用了非常悠闲的方法. (2认同)

Chi*_*ael 5

这非常简单.string和int之间没有隐式转换.三元运算符需要最后两个操作数具有相同的类型.

尝试:

Write(two > six ? two.ToString() : "6");
Run Code Online (Sandbox Code Playgroud)

  • 问题是导致Java和C#之间差异的原因.这不回答这个问题. (11认同)