Ответ 1
Необычно, как это может показаться, просто следуя правилам спецификации языка С#.
Из раздела 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
не имеет специальной оболочки в компиляторе С#, это будет эффективно:
string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;
Таким образом, создаются две ненужные промежуточные строки. Однако, поскольку в компиляторе есть специальная поддержка, он действительно может скомпилировать вышеуказанное как:
string x = string.Concat(a, b, c, d);
который может создать только одну строку точно соответствующей длины, скопировав все данные ровно один раз. Ницца.