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

Difference between C# and Java's ternary 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

但是,相同的代码在 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# 中缺少什么语言功能?如果有,为什么不添加?

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

尝试:

Write(two > six ? two.ToString() : "6");

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

另一方面,在 C# 中,int 是原语,不存在到 string 或任何其他 object.

的隐式转换

查看 C# 5 语言规范第 7.14 节:条件运算符 我们可以看到以下内容:

  • If x has type X and y has type Y then

    • If an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.

    • If an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.

    • Otherwise, no expression type can be determined, and a compile-time error occurs

换句话说:它会尝试查找 x 和 y 是否可以转换为 eachother,如果不能,则会出现编译错误。在我们的例子中,intstring 没有显式或隐式转换,因此无法编译。

将此与 Java 7 Language Specification section 15.25: Conditional Operator 对比:

  • If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression. (NO)
  • If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T. (NO)
  • If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type. (NO)
  • Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases: (NO)
  • Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2.
    The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7). (YES)

并且,查看 section 15.12.2.7. Inferring Type Arguments Based on Actual Arguments 我们可以看到它试图找到一个共同的祖先,该祖先将用作用于通过 Object 登陆的调用的类型。 Object 是一个可接受的参数,因此调用会起作用。

关于泛型部分:

two > six ? new List<int>() : new List<string>()

在 C# 中,编译器会尝试 右侧的表达式部分转换为一些通用类型;由于 List<int>List<string> 是两种不同的构造类型,因此无法将一种转换为另一种。

在Java中,编译器试图找到一个共同的超类型而不是转换,所以代码的编译涉及隐式使用wildcards and type erasure ;

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

的编译类型为 ArrayList<?>(实际上,它也可以是 ArrayList<? extends Serializable>ArrayList<? extends Comparable<?>>,这取决于使用上下文,因为它们都是通用的泛型超类型)和运行时类型原始 ArrayList(因为它是常见的原始超类型)。

例如(test it yourself),

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

给出的答案很好;我会向他们补充说,C# 的这条规则是更通用的设计指南的结果。当要求从多个选项之一推断表达式的类型时,C# 选择其中唯一的最佳选项。也就是说,如果您给 C# 一些选择,例如 "Giraffe, Mammal, Animal",那么它可能会选择最一般的——Animal——或者它可能会选择最具体的——Giraffe——这取决于具体情况。但它必须选择实际给出的选择之一。 C# 从不说 "my choices are between Cat and Dog, therefore I will deduce that Animal is the best choice"。这不是给定的选择,因此 C# 无法选择它。

在三元运算符的情况下,C# 尝试选择更通用的 int 和 string 类型,但都不是更通用的类型。 C# 没有选择一开始就不是选择的类型,例如对象,而是决定无法推断出任何类型。

我还注意到,这符合 C# 的另一个设计原则:如果有问题,请告诉开发人员。语言不说"I'm going to guess what you meant and muddle on through if I can"。语言说 "I think you've written something confusing here, and I'm going to tell you about that."

此外,我注意到 C# 不会从 变量 推理到 赋值 ,而是从另一个方向推理。 C#没有说"you're assigning to an object variable therefore the expression must be convertible to object, therefore I will make sure that it is"。相反,C# 表示 "this expression must have a type, and I must be able to deduce that the type is compatible with object"。由于表达式没有类型,因此产生错误。