Оскільки true
це не рядковий тип, то як null + true
рядок?
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
У чому причина цього?
Оскільки true
це не рядковий тип, то як null + true
рядок?
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
У чому причина цього?
Відповіді:
Як це не здається дивним, це просто дотримання правил із специфікації мови 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
, але в цьому випадку невдача перетворення в рядок робить весь вираз помилкою, а значить, не має типу.
x
має тип string
. Зауважте, що тут використовується підпис string operator+(string, object)
- це перетворення bool
на object
(що нормально), а не на string
.
Причина тому, що коли ви вводите +
тодішні правила зв’язування оператора C #, вступають у дію. Він буде враховувати набір +
операторів, доступних, і вибере найкраще перевантаження. Одним із таких операторів є наступний
string operator +(string x, object y)
Ця перевантаження сумісна з типами аргументів у виразі null + true
. Отже, він обраний як оператор і оцінюється як по суті, ((string)null) + true
що оцінює значення "True"
.
Розділ 7.7.4 специфікації мови C # містить деталі навколо цієї резолюції.
operator+
для string
. Натомість він існує лише на увазі компілятора, і він просто переводить його вниз на виклики дляstring.Concat
Компілятор виходить на пошук оператора + (), який спочатку може взяти нульовий аргумент. Жоден із стандартних типів значень не кваліфікується, null не є дійсним значенням для них. Єдиний збіг System.String.operator + (), двозначності немає.
Другим аргументом цього оператора є також рядок. Це іде kapooey, не може неявно перетворити bool у рядок.
Цікаво, що за допомогою 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;
насправді не приймається компілятором.
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
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
...
Причиною цього є зручність (об'єднання рядків є загальним завданням).
Як сказав BoltClock, оператор '+' визначається на числових типах, рядках і може бути визначений і для наших власних типів (перевантаження оператора).
Якщо для типів аргументу немає перевантаженого оператора "+", і вони не є числовими типами, компілятор за замовчуванням об'єднує рядки.
Компілятор вставляє виклик, String.Concat(...)
коли ви об'єднуєтесь за допомогою "+", і реалізація Concat викликає ToString для кожного об'єкта, переданого в нього.