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


94

Я новачок на C #, і я просто стикаюся з проблемою. Існує різниця між C # та Java, коли йдеться про тернарний оператор ( ? :).

Чому в наступному сегменті коду 4-й рядок не працює? Компілятор показує повідомлення про помилку there is no implicit conversion between 'int' and 'string'. 5-й рядок також не працює. Обидва Lists є об'єктами, чи не так?

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 на рядок. Вам потрібно поставити явне перетворення типу на ньому.
Джей

2
@ blackr1234 Оскільки an intне є об'єктом. Це примітив.
Code-Apprentice

3
@ Code-Apprentice, так, вони насправді дуже різні - C # має реалізацію часу виконання, тому списки не пов'язані між собою. Ох, і ви також можете додати загальну коваріацію / противаріантність інтерфейсу, якщо хочете, щоб представити якийсь рівень взаємозв'язку;)
Лукас Тшесневський

3
На користь OP: стирання типу в 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. ( NO )
  • Якщо один із другого та третього операндів має нульовий тип, а тип іншого - тип посилання, то тип умовного виразу є тим типом посилання. ( НІ )
  • В іншому випадку, якщо другий і третій операнди мають типи, які можна конвертувати (§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 # ніколи не говорить "мій вибір між Кішкою та Собакою, тому я буду робити висновок, що Тварина - найкращий вибір". Це не був вибір, тому C # не може його вибрати.

У випадку з тернарним оператором C # намагається вибрати більш загальний тип int та string, але не є більш загальним типом. Замість того, щоб вибрати тип, який спочатку не був вибором, наприклад, об'єкт, C # вирішує, що жоден тип не можна зробити висновком.

Я також зазначаю, що це відповідає іншому принципу дизайну C #: якщо щось виглядає неправильно, повідомте розробника. Мова не говорить "Я збираюся вгадати, що ти мав на увазі, і поплутати, якщо зможу". Мова говорить: "Я думаю, ви написали тут щось заплутане, і я розповім вам про це".

Крім того, я зауважу, що C # не міркує зі змінної до призначеного значення , а скоріше в іншому напрямку. C # не говорить "ви призначаєте змінну об'єкта, тому вираз повинен бути конвертованим до об'єкта, тому я переконаюсь, що він є". Швидше за все, C # говорить "цей вираз повинен мати тип, і я повинен мати змогу зробити висновок, що тип сумісний з об'єктом". Оскільки вираз не має типу, виникає помилка.


6
Я пройшов повз перший абзац, дивуючись, чому раптова коваріація / противаріація цікавить, потім я почав більше домовлятися щодо другого абзацу, потім побачив, хто написав відповідь ... Мав би здогадатися раніше. Ви коли-небудь помічали , скільки саме ви використовуєте як приклад "Жирафа" ? Вам, мабуть, дуже подобаються Жирафи.
Pharap

21
Жирафи чудові!
Ерік Ліпперт

9
@Pharap: Якщо говорити серйозно, є педагогічні причини, чому я так часто користуюся жирафом. Перш за все, жирафи добре відомі у всьому світі. По-друге, це своєрідне веселе слово, і вони настільки дивно виглядають, що це запам’ятовується образ. По-третє, використання, скажімо, "жирафа" та "черепахи" в одній і тій же аналогії наголошує на тому, що "ці два класи, хоча вони можуть мати спільний базовий клас, є дуже різними тваринами з дуже різними характеристиками". Запитання про системи типів часто містять приклади, коли типи логічно дуже схожі, що відводить вашу інтуїцію не в той бік.
Ерік Ліпперт

7
@Pharap: Тобто, запитання, як правило, полягає приблизно в тому, "чому перелік заводів постачальників рахунків-фактур не може бути використаний як список заводів постачальників рахунків-фактур?" і ваша інтуїція говорить "так, це в основному одне і те ж", але коли ви говорите "чому не можна використовувати кімнату, повну жирафів, як кімнату, повну ссавців?" тоді стає набагато більше інтуїтивною аналогією говорити "тому що кімната, повна ссавців, може містити тигра, кімната, повна жирафів, не може".
Ерік Ліпперт

6
@Eric Я спочатку зіткнувся з цією проблемою int? b = (a != 0 ? a : (int?) null). Є також це питання , разом із усіма пов’язаними питаннями збоку. Якщо ви продовжуєте переходити за посиланнями, їх досить багато. Для порівняння, я ніколи не чув, щоб хтось стикався з реальною проблемою з тим, як Java це робить.
BlueRaja - Danny Pflughoeft

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<?>>, залежно від контексту використання, оскільки вони є як загальними загальними супертипами), так і типом середовища виконання ArrayList(оскільки це загальний супертип типу raw).

Наприклад (протестуйте самі) ,

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

2
@ blackr1234 точно: я не хотів повторювати те, що вже було сказано в інших відповідях, але тернарний вираз потрібно оцінити за типом, і компілятор не намагатиметься знайти "найнижчий загальний знаменник" з них.
Mathieu Guindon

2
Власне, мова йде не про стирання типів, а про типи підстановок. Видалення ArrayList<String>та ArrayList<Integer>буде ArrayList(необроблений тип), але виведений тип для цього потрійного оператора ArrayList<?>(тип підстановки). Більш загально, що середовище виконання Java реалізує загальні засоби за допомогою стирання типу, не впливає на тип компіляції виразу.
meriton

1
@vaxquis дякую! Я хлопець C #, дженерики Java мене бентежать ;-)
Mathieu Guindon

2
Насправді тип two > six ? new ArrayList<Integer>() : new ArrayList<String>()- 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

2
Я не вважаю, що це правильно, оскільки в C # цілком можливо вкласти int в об'єкт. Я вважаю, що тут різниця полягає в тому, що C # просто застосовує дуже спокійний підхід до визначення, дозволено це чи ні.
Jeroen Vannevel

9
Це не має нічого спільного з автобоксом, що траплялося б так само у C #. Різниця полягає в тому, що C # не намагається знайти загальний супертип, тоді як Java (оскільки це лише Java 5!) Робить це. Ви можете легко перевірити це, спробувавши побачити, що трапиться, якщо ви спробуєте це за допомогою 2 користувацьких класів (які обидва успадковуються від об'єкта). Також відбувається неявне перетворення з 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.
Holger

5

Це досить просто. Немає неявного перетворення між рядком та int. тернарному оператору потрібні два останні операнди, щоб мати однаковий тип.

Спробуйте:

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

11
Питання в тому, що спричиняє різницю між Java та C #. Це не дає відповіді на запитання.
Jeroen Vannevel
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.