Як null + true є рядком?


112

Оскільки trueце не рядковий тип, то як null + trueрядок?

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

У чому причина цього?


27
Ви робите щось, що не має сенсу, а потім не подобається повідомлення, яке створює компілятор? Це недійсний код C # ... що саме ви цього хотіли?
Хоган

19
@Hogan: Код здається випадковим і достатньо академічним, щоб питання ставити з чистої цікавості. Лише здогадуюсь ...
BoltClock

8
null + true повертає 1 у JavaScript.
Fatih Acet

Відповіді:


147

Як це не здається дивним, це просто дотримання правил із специфікації мови C #.

З розділу 7.3.4:

Операція форми x op y, де op - це бінарний оператор, що можна завантажити, x - вираз типу X, а y - вираз типу Y, обробляється наступним чином:

  • Визначається набір визначених користувачем операторів, що надаються X і Y для оператора операції op (x, y). Набір складається з об'єднання операторів-кандидатів, що надаються X, та операторів-кандидатів, передбачених Y, кожен визначається, використовуючи правила §7.3.5. Якщо X і Y однотипні, або якщо X і Y похідні від загального базового типу, то спільні кандидати-оператори трапляються в комбінованому наборі лише один раз.
  • Якщо набір визначених користувачем операторів не є порожнім, це стає набором операторів-кандидатів для операції. В іншому випадку, попередньо визначені реалізації бінарних операторів, включаючи їх підняті форми, стають набором операторів-кандидатів для операції. Заздалегідь визначені реалізації даного оператора вказані в описі оператора (§7.8 - §7.12).
  • Правила вирішення перевантаження в §7.5.3 застосовуються до набору операторів-кандидатів для вибору найкращого оператора стосовно списку аргументів (x, y), і цей оператор стає результатом процесу усунення перевантаження. Якщо у вирішенні перевантаження не вдалося вибрати одного найкращого оператора, виникає помилка в часі прив’язки.

Отже, давайте пройдемося по черзі.

X - це нульовий тип тут - або зовсім не тип, якщо ви хочете думати про це саме так. Це не надає жодних кандидатів. Y є bool, що не забезпечує жодних визначених користувачем +операторів. Отже, перший крок не знаходить визначених користувачем операторів.

Потім компілятор переходить до другої точки кулі, переглядаючи заздалегідь визначений бінарний оператор + реалізації та їх підняті форми. Вони наведені в розділі 7.8.4 специфікації.

Якщо ви переглянете ці заздалегідь задані оператори, єдиним застосовним є string operator +(string x, object y). Отже, набір кандидатів має один запис. Це робить остаточну точку кулі дуже простою ... Роздільна здатність перевантаження вибирає цього оператора, даючи загальний тип вираження string.

Цікавим моментом є те, що це відбудеться, навіть якщо є інші визначені користувачем оператори, доступні для не згаданих типів. Наприклад:

// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;

Це добре, але він не використовується для нульового літералу, оскільки компілятор не знає шукати Foo. Це знає лише те, stringщо це попередньо визначений оператор, чітко вказаний у специфікації. (Насправді, це не оператор, визначений типом рядка ... 1 ) Це означає, що це не вдасться скомпілювати:

// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;

Інші типи другого операнда, звичайно, використовуватимуть деякі інші оператори:

var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>

1 Вам може бути цікаво, чому немає оператора string +. Це розумне питання, і я лише здогадуюсь на відповідь, але врахуйте цей вираз:

string x = a + b + c + d;

Якби stringв компіляторі C # не було спеціального кожуха, це закінчиться так само ефективно:

string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;

Тож створено дві непотрібні проміжні рядки. Однак, оскільки в компіляторі є спеціальна підтримка, він насправді може зібрати вищезгадане як:

string x = string.Concat(a, b, c, d);

який може створити лише один рядок точно потрібної довжини, копіюючи всі дані рівно один раз. Приємно.


"надання загального типу вираження рядка" Я просто не згоден. Помилка пов'язана з тим, що вона trueне конвертована в string. Якщо вираз був дійсним, був би тип string, але в цьому випадку невдача перетворення в рядок робить весь вираз помилкою, а значить, не має типу.
леппі

6
@leppie: Вираз є дійсним, і його тип рядка. Спробуйте "var x = null + true;" - вона компілює та xмає тип string. Зауважте, що тут використовується підпис string operator+(string, object)- це перетворення boolна object(що нормально), а не на string.
Джон Скіт

Дякую, Джон, зрозумій зараз. Розділ BTW 14.7.4 у версії 2 специфікації.
леппі

@leppie: Це в нумерації ECMA? Це завжди було зовсім інакше :(
Джон Скіт

47
за 20 хвилин. Він написав усе це за 20 хвилин. Він повинен написати книгу чи щось ... о, зачекайте.
Епага

44

Причина тому, що коли ви вводите +тодішні правила зв’язування оператора C #, вступають у дію. Він буде враховувати набір +операторів, доступних, і вибере найкраще перевантаження. Одним із таких операторів є наступний

string operator +(string x, object y)

Ця перевантаження сумісна з типами аргументів у виразі null + true. Отже, він обраний як оператор і оцінюється як по суті, ((string)null) + trueщо оцінює значення "True".

Розділ 7.7.4 специфікації мови C # містить деталі навколо цієї резолюції.


1
Я помічаю, що IL-генерація переходить до виклику Concat. Я думав, що побачу дзвінок на функцію + оператор. Це просто вбудована оптимізація?
квентин-зірин

1
@qstarin я вважаю , що причина в тому , що не існує на насправдіoperator+ для string. Натомість він існує лише на увазі компілятора, і він просто переводить його вниз на виклики дляstring.Concat
JaredPar

1
@qstarin @JaredPar: У своїй відповіді я трохи більше зайнявся цим питанням.
Джон Скіт

11

Компілятор виходить на пошук оператора + (), який спочатку може взяти нульовий аргумент. Жоден із стандартних типів значень не кваліфікується, null не є дійсним значенням для них. Єдиний збіг System.String.operator + (), двозначності немає.

Другим аргументом цього оператора є також рядок. Це іде kapooey, не може неявно перетворити bool у рядок.


10

Цікаво, що за допомогою Reflector перевірити те, що створюється, наступний код:

string b = null + true;
Console.WriteLine(b);

перетворюється в це компілятором:

Console.WriteLine(true);

Обґрунтування цієї "оптимізації", я мушу сказати трохи дивно, і не римується з вибором оператора, якого я б очікував.

Також такий код:

var b = null + true; 
var sb = new StringBuilder(b);

перетворюється на

string b = true; 
StringBuilder sb = new StringBuilder(b);

де string b = true;насправді не приймається компілятором.


8

nullбуде приведено до null string, і є неявний перетворювач з bool в string, тому trueбуде +передано рядок до string, а потім буде застосовано оператор: це як: string str = "" + true.ToString ();

якщо ви перевірите це за допомогою Ildasm:

string str = null + true;

це як нижче:

.locals init ([0] string str)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  box        [mscorlib]System.Boolean
  IL_0007:  call       string [mscorlib]System.String::Concat(object)
  IL_000c:  stloc.0

5
var b = (null + DateTime.Now); // String
var b = (null + 1);            // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type

Божевільний ?? Ні, за цим повинна бути причина.

Хтось дзвонить Eric Lippert...


5

Причиною цього є зручність (об'єднання рядків є загальним завданням).

Як сказав BoltClock, оператор '+' визначається на числових типах, рядках і може бути визначений і для наших власних типів (перевантаження оператора).

Якщо для типів аргументу немає перевантаженого оператора "+", і вони не є числовими типами, компілятор за замовчуванням об'єднує рядки.

Компілятор вставляє виклик, String.Concat(...)коли ви об'єднуєтесь за допомогою "+", і реалізація Concat викликає ToString для кожного об'єкта, переданого в нього.

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