Разница между С# и тернарным оператором Java (?:)

Я новичок в С#, и я просто столкнулся с проблемой. Существует разница между С# и 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

Какая языковая функция в С# отсутствует? Если да, то почему он не добавлен?

Ответ 1

Просматривая раздел 7.14 "Условные обозначения языка С# 5: Условный оператор", мы можем видеть следующее:

  • Если x имеет тип X, а y имеет тип Y, то

    • Если неявное преобразование (§6.1) существует от X до Y, но не от Y до X, то Y является типом условное выражение.

    • Если неявное преобразование (§6.1) существует из Y в X, но не от X до Y, то X является типом условное выражение.

    • В противном случае тип выражения не может быть определен, и возникает ошибка времени компиляции

Другими словами: он пытается определить, может ли x и y быть преобразован в eachother, а если нет, возникает ошибка компиляции. В нашем случае int и string не имеют явного или неявного преобразования, поэтому он не будет компилироваться.

Сравните это с Java 7 Language Specification section 15.25: Условный оператор:

  • Если второй и третий операнды имеют один и тот же тип (который может быть нулевым типом), то это тип условного выражения. (НЕТ)
  • Если один из второго и третьего операндов имеет примитивный тип T, а тип другого - результат применения преобразования бокса (п. 5.1.7) в T, то тип условного выражения равен T. ( НЕТ)
  • Если один из второго и третьего операндов имеет нулевой тип, а тип другого - ссылочный тип, то тип условного выражения является ссылочным типом. ( НЕТ)
  • В противном случае, если второй и третий операнды имеют типы, которые являются конвертируемыми (п. 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 является приемлемым аргументом, поэтому вызов будет работать.

Ответ 2

Данные ответы хороши; Я бы добавил к ним, что это правило С# является следствием более общего руководства по проектированию. При запросе вывести тип выражения из одного из нескольких вариантов, С# выбирает уникальные лучшие из них. То есть, если вы дадите С# некоторые варианты, такие как "Жираф, млекопитающее, животное", тогда он может выбрать самый общий - Animal - или он может выбрать наиболее специфичный - Жираф - в зависимости от обстоятельств. Но он должен выбрать один из вариантов, которые он фактически дал. С# никогда не говорит: "Мой выбор между Котом и Собакой, поэтому я выберу, что Animal - лучший выбор". Это был не выбор, поэтому С# не может его выбрать.

В случае тройного оператора С# пытается выбрать более общий тип int и string, но ни один из них не является более общим типом. Вместо того, чтобы выбирать тип, который не был выбран в первую очередь, подобно объекту, С# решает, что тип не может быть выведен.

Я также отмечаю, что это соответствует другому принципу дизайна С#: если что-то не так, расскажите разработчику. Язык не говорит: "Я собираюсь угадать, что вы имели в виду и путаться, если смогу". Язык говорит: "Я думаю, что вы написали что-то запутанное здесь, и я расскажу вам об этом".

Кроме того, я отмечаю, что С# не указывает на переменную на назначенное значение, а на другое направление. С# не говорит "вы назначаете объектную переменную, поэтому выражение должно быть конвертируемым в объект, поэтому я буду уверен, что оно". Скорее, С# говорит, что "это выражение должно иметь тип, и я должен уметь выводить, что тип совместим с объектом". Поскольку выражение не имеет типа, возникает ошибка.

Ответ 3

Что касается части генериков:

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

В С# компилятор пытается преобразовать правые части выражения в некоторый общий тип; поскольку List<int> и List<string> - два разных построенных типа, нельзя преобразовать их в другой.

В Java компилятор пытается найти общий супертип вместо преобразования, поэтому компиляция кода подразумевает неявное использование wildcards и стирать стили;

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

имеет тип компиляции 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

Ответ 4

Это довольно просто. Нет никакого неявного преобразования между строкой и int. тройной оператор требует, чтобы последние два операнда имели один и тот же тип.

Try:

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

Ответ 5

В Java и С# (и большинстве других языков) результат выражения имеет тип. В случае тройного оператора существуют два возможных подвыражения, оцененных для результата, и оба должны иметь один и тот же тип. В случае Java переменная int может быть преобразована в Integer путем автобоксинга. Теперь, поскольку оба Integer и String наследуются от Object, их можно преобразовать в один и тот же тип путем простого сужения преобразования.

С другой стороны, в С# a int является примитивным и не подразумевается преобразование в String или любое другое Object.