Как строка 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 # ... что именно вы от него хотели?
Hogan
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 являются производными от общего базового типа, то общие операторы-кандидаты встречаются в объединенном наборе только один раз.
  • Если набор определяемых пользователем операторов-кандидатов не пуст, он становится набором операторов-кандидатов для операции. В противном случае предопределенные реализации бинарных операторов op, включая их расширенные формы, становятся набором операторов-кандидатов для операции. Предопределенные реализации данного оператора указаны в описании оператора (с §7.8 по §7.12).
  • Правила разрешения перегрузки из §7.5.3 применяются к набору операторов-кандидатов, чтобы выбрать лучший оператор по отношению к списку аргументов (x, y), и этот оператор становится результатом процесса разрешения перегрузки. Если при разрешении перегрузки не удается выбрать один лучший оператор, возникает ошибка времени привязки.

Итак, давайте рассмотрим это по очереди.

X - это нулевой тип здесь - или вообще не тип, если вы хотите так думать. Он не предоставляет никаких кандидатов. Y is bool, который не предоставляет никаких пользовательских +операторов. Итак, на первом этапе пользовательские операторы не обнаруживаются.

Затем компилятор переходит ко второму пункту, просматривая предопределенные бинарные реализации operator + и их расширенные формы. Они перечислены в разделе 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 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, но в этом случае невозможность преобразования в строку делает все выражение ошибкой и, следовательно, не имеет типа.
leppie
6
@leppie: Выражение является действительным, и его тип строки. Попробуйте «var x = null + true;» - он компилируется и xимеет тип string. Обратите внимание, что здесь используется подпись string operator+(string, object)- она ​​преобразуется boolв object(что нормально), а не в string.
Джон Скит,
Спасибо, Джон, теперь пойми. Кстати, раздел 14.7.4 в версии 2 спецификации.
leppie
@leppie: Это есть в нумерации ECMA? Это всегда было очень разным :(
Джон Скит
47
через 20 мин. Он написал все это за 20 минут. Он должен написать книгу или что-нибудь ... о, подождите.
Epaga
44

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

string operator +(string x, object y)

Эта перегрузка совместима с типами аргументов в выражении null + true. Следовательно, он выбирается как оператор и оценивается по существу как ((string)null) + trueвычисляющий значение "True".

Раздел 7.7.4 спецификации языка C # содержит подробные сведения об этом разрешении.

JaredPar
источник
1
Я заметил, что сгенерированный IL идет прямо на вызов Concat. Я думал, что увижу там вызов функции оператора +. Будет ли это просто встраиваемая оптимизация?
quentin-starin
1
@qstarin я считаю , что причина в том , что не существует на самом делеoperator+ для string. Вместо этого он существует только в уме компилятора и просто переводит его в призывыstring.Concat
JaredPar
1
@qstarin @JaredPar: Я подробно остановился на этом в своем ответе.
Джон Скит,
11

Компилятор ищет оператор + (), который сначала может принимать нулевой аргумент. Ни один из стандартных типов значений не подходит, значение null для них недопустимо. Единственное совпадение - System.String.operator + (), двусмысленности нет.

Второй аргумент этого оператора также является строкой. Это капуэй, не может неявно преобразовать 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будет преобразовано в пустую строку, и есть неявный преобразователь из bool в строку, поэтому trueбудет преобразован в строку, а затем +будет применен оператор: это похоже на: 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...

decyclone
источник
5

Причина этого - удобство (объединение строк - обычная задача).

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

Если для типов аргументов нет перегруженного оператора '+' и они не являются числовыми типами, компилятор по умолчанию использует конкатенацию строк.

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

Quentin-Starin
источник