Поскольку 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 являются производными от общего базового типа, то общие операторы-кандидаты встречаются в объединенном наборе только один раз.
- Если набор определяемых пользователем операторов-кандидатов не пуст, он становится набором операторов-кандидатов для операции. В противном случае предопределенные реализации бинарных операторов 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
, но в этом случае невозможность преобразования в строку делает все выражение ошибкой и, следовательно, не имеет типа.
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 + (), двусмысленности нет.
Второй аргумент этого оператора также является строкой. Это капуэй, не может неявно преобразовать 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
будет преобразовано в пустую строку, и есть неявный преобразователь из 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
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 для каждого переданного в него объекта.