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

94

Я новичок в 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 # отсутствует? Если есть, то почему не добавляется?

черный
источник
4
Согласно msdn.microsoft.com/en-us/library/ty67wk28.aspx «Либо типы first_expression и second_expression должны быть одинаковыми, либо должно существовать неявное преобразование из одного типа в другой».
Егор
2
Я давно не изучал C #, но разве в сообщении, которое вы цитируете, не говорится об этом? Две ветви троичной системы должны возвращать один и тот же тип данных. Вы даете ему int и String. C # не знает, как автоматически преобразовать int в String. Вам нужно поставить на него явное преобразование типа.
Джей
2
@ blackr1234 Потому что intэто не объект. Это примитивно.
Code-Apprentice
3
@ Code-Apprentice да, они действительно очень разные - C # имеет реификацию среды выполнения, поэтому списки не связаны между собой. О, и вы также можете добавить в смесь общую ковариацию / контравариантность интерфейса, если хотите, чтобы ввести некоторый уровень взаимосвязи;)
Лукас Тшесневски
3
Для получения выгоды в ФП в: типа стирания в Java означает , что ArrayList<String>и ArrayList<Integer>стало только ArrayListв байткод. Это означает, что они выглядят во время выполнения одного и того же типа . Видимо в C # они разных типов.
Code-Apprentice

Ответы:

106

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

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

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

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

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

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

Сравните это с разделом 15.25 спецификации языка Java 7: Условный оператор :

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

И, глядя на раздел 15.12.2.7. Вывод аргументов типа На основе фактических аргументов мы видим, что он пытается найти общего предка, который будет служить типом, используемым для вызова, с которым он приземляется Object. Object - приемлемый аргумент, поэтому вызов будет работать.

Йерун Ванневель
источник
1
Я прочитал спецификацию C #, в которой говорится, что должно быть неявное преобразование хотя бы в одном направлении. Ошибка компилятора возникает только при отсутствии неявного преобразования в любом направлении.
Code-Apprentice
1
Конечно, это по-прежнему наиболее полный ответ, поскольку вы цитируете прямо из спецификаций.
Code-Apprentice
Фактически это было изменено только в Java 5 (я думаю, могло быть и 6). До этого Java вел себя точно так же, как C #. Из-за способа реализации перечислений это, вероятно, вызвало даже больше сюрпризов в Java, чем в C #, хотя это хорошее изменение с самого начала.
Voo
@Voo: FYI, Java 5 и Java 6 имеют одинаковую версию спецификации ( спецификация языка Java , третье издание), поэтому она определенно не изменилась в Java 6.
ruakh
86

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

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

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

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

Эрик Липперт
источник
6
Я прошел мимо первого абзаца, гадая, почему внезапно возник интерес к ковариации / контравариантности, затем я начал больше соглашаться по второму абзацу, затем я увидел, кто написал ответ ... Надо было догадаться раньше. Вы когда-нибудь замечали , как часто вы используете «Жираф» в качестве примера ? Вы, должно быть, действительно любите жирафов.
Pharap
21
Жирафы классные!
Эрик Липперт
9
@Pharap: Если серьезно, есть педагогические причины, по которым я так часто использую жирафов. Во-первых, во всем мире хорошо известны жирафы. Во-вторых, это своего рода забавное слово, и они настолько странно выглядят, что это запоминающийся образ. В-третьих, использование, скажем, «жирафа» и «черепахи» в той же аналогии сильно подчеркивает, что «эти два класса, хотя они могут иметь общий базовый класс, являются очень разными животными с очень разными характеристиками». Вопросы о системах типов часто содержат примеры, когда типы логически очень похожи, что неверно влияет на вашу интуицию.
Эрик Липперт,
7
@Pharap: То есть вопрос обычно звучит примерно так: "Почему нельзя использовать список фабрик поставщиков счетов-фактур как список фабрик поставщиков счетов?" и ваша интуиция говорит: «Да, это в основном одно и то же», но когда вы говорите: «Почему нельзя использовать комнату, полную жирафов, как комнату, полную млекопитающих?» тогда это становится гораздо более интуитивной аналогией, если сказать: «потому что комната, полная млекопитающих, может содержать тигра, а комната, полная жирафов, не может».
Эрик Липперт,
6
@Eric Я изначально столкнулся с этой проблемой int? b = (a != 0 ? a : (int?) null). Также есть этот вопрос вместе со всеми связанными вопросами сбоку. Если вы продолжите переходить по ссылкам, их довольно много. Для сравнения, я никогда не слышал, чтобы кто-то сталкивался с реальной проблемой, связанной с тем, как это делает Java.
BlueRaja - Дэнни Пфлугхофт
24

Что касается дженериков:

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

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

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

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
Матье Гиндон
источник
Собственно Write(two > six ? new List<object>() : new List<string>());тоже не работает.
blackr1234 05
2
@ blackr1234 точно: я не хотел повторять то, что уже было сказано в других ответах, но троичное выражение должно оцениваться как тип, и компилятор не будет пытаться найти «наименьший общий знаменатель» из двух.
Mathieu Guindon
2
На самом деле, речь идет не о стирании типа, а о типах подстановочных знаков. Стирание ArrayList<String>и ArrayList<Integer>будет ArrayList(исходный тип), но предполагаемый тип для этого тернарного оператора - ArrayList<?>(тип подстановочного знака). В более общем плане то, что среда выполнения Java реализует универсальные шаблоны посредством стирания типа, не влияет на тип выражения во время компиляции .
meriton 06
1
@vaxquis спасибо! Я парень C #, дженерики Java сбивают меня с толку ;-)
Мэтью Гуиндон,
2
Фактически, тип two > six ? new ArrayList<Integer>() : new ArrayList<String>()is, ArrayList<? extends Serializable&Comparable<?>>что означает, что вы можете назначить его как переменной типа, ArrayList<? extends Serializable>так и переменной типа ArrayList<? extends Comparable<?>>, но, конечно же ArrayList<?>, что эквивалентно ArrayList<? extends Object>, также работает.
Holger
6

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

С другой стороны, в C # an intявляется примитивом, и не существует неявного преобразования в stringили любое другое object.

Код-ученик
источник
Спасибо за ответ. Автобокс - это то, о чем я сейчас думаю. Разве C # не поддерживает автобокс?
blackr1234 05
2
Я не думаю, что это правильно, так как упаковка int к объекту вполне возможна в C #. Разница здесь, я считаю, в том, что C # просто использует очень непринужденный подход к определению, разрешено это или нет.
Jeroen Vannevel
9
Это не имеет ничего общего с автоматическим боксом, который может происходить точно так же в C #. Разница в том, что C # не пытается найти общий супертип, в то время как Java (начиная только с Java 5!) Делает это. Вы можете легко проверить это, пытаясь увидеть, что произойдет, если вы попробуете это с двумя настраиваемыми классами (которые, очевидно, наследуются от объекта). Также есть неявное преобразование из intв object.
Voo
3
И еще немного для придирки: преобразование из Integer/Stringв Objectне является сужающим преобразованием, это полная противоположность :-)
Voo
1
Integerа Stringтакже реализовать, Serializableи Comparableпоэтому назначения для любого из них также будут работать, например, Comparable<?> c=condition? 6: "6";или List<? extends Serializable> l = condition? new ArrayList<Integer>(): new ArrayList<String>();являются допустимым кодом Java.
Хольгер
5

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

Пытаться:

Write(two > six ? two.ToString() : "6");
КиралМайкл
источник
11
Вопрос в том, в чем разница между Java и C #. Это не отвечает на вопрос.
Jeroen Vannevel