Почему допустимо объединять пустые строки, но нельзя вызывать «null.ToString ()»?


126

Это действительный код C #

var bob = "abc" + null + null + null + "123";  // abc123

Это недопустимый код C #

var wtf = null.ToString(); // compiler error

Почему первое утверждение действительно?


97
Мне кажется странным, что тебе null.ToString()дали имя wtf. Почему это вас удивляет? Вы не можете вызвать метод экземпляра, если вам не из чего его вызывать.
BoltClock

11
@BoltClock: Конечно, можно вызвать метод экземпляра для нулевого экземпляра. Просто невозможно в C #, но очень актуально в CLR :)
leppie

1
Ответы на String.Concat почти правильные. Фактически, конкретный пример в вопросе - это пример сворачивания констант , и нули в первой строке удаляются компилятором, т.е. они никогда не оцениваются во время выполнения, потому что они больше не существуют - компилятор стер их. Я написал небольшой монолог обо всех правилах конкатенации и константного сворачивания строк здесь для получения дополнительной информации: stackoverflow.com/questions/9132338/… .
Крис Шейн

77
вы не можете ничего добавить к строке, но вы не можете сделать строку из ничего.
RGB

Второе утверждение неверно? class null_extension { String ToString( Object this arg ) { return ToString(arg); } }
ctrl-alt-delor

Ответы:


163

Причина первой работы:

Из MSDN :

В операциях конкатенации строк компилятор C # обрабатывает пустую строку так же, как пустую строку, но не преобразует значение исходной пустой строки.

Дополнительная информация о бинарном операторе + :

Бинарный оператор + выполняет конкатенацию строк, если один или оба операнда относятся к строковому типу.

Если операнд конкатенации строк равен нулю, подставляется пустая строка. В противном случае любой нестроковый аргумент преобразуется в его строковое представление путем вызова виртуального ToStringметода, унаследованного от объекта типа.

Если ToStringвозвращается null, подставляется пустая строка.

Причина ошибки в секундах:

null (справочник C #) - ключевое слово null - это литерал, представляющий пустую ссылку, не относящуюся ни к какому объекту. null - это значение по умолчанию для переменных ссылочного типа.


1
Тогда это сработает? var wtf = ((String)null).ToString();Я недавно работаю на Java, где возможно приведение null, прошло некоторое время с тех пор, как я работал с C #.
Jochem

14
@Jochem: Вы все еще пытаетесь вызвать метод для нулевого объекта, поэтому я предполагаю, что нет. Думайте об этом как null.ToString()против ToString(null).
Свиш

@Svish да, теперь я снова думаю об этом, это нулевой объект, так что вы правы, это не сработает. Этого не было бы и в Java: исключение нулевого указателя. Неважно. Спасибо за ответ! [изменить: протестировал его на Java: NullPointerException. С той разницей, что с приведением он компилируется, а без приведения - нет].
Jochem

обычно нет причин для намеренного объединения или ToString ключевого слова null. Если вам нужна пустая строка, используйте string.Empty. если вам нужно проверить, имеет ли строковая переменная значение null, вы можете использовать (myString == null) или string.IsNullOrEmpty (myString). В качестве альтернативы для преобразования нулевой строковой переменной в string.Empty используйте myNewString = (myString == null ?? string.Empty)
csauve

Приведение @Jochem заставит его скомпилировать, но действительно вызовет исключение NullReferenceException во время выполнения
jeroenh

108

Поскольку +оператор в C # выполняет внутреннее преобразование в String.Concatстатический метод. И этот метод обрабатывается nullкак пустая строка. Если вы посмотрите на исходный код String.Concatв Reflector, вы увидите:

// while looping through the parameters
strArray[i] = (str == null) ? Empty : str;
// then concatenate that string array

(MSDN тоже упоминает об этом: http://msdn.microsoft.com/en-us/library/k9c94ey1.aspx )

С другой стороны, ToString()это метод экземпляра, который нельзя вызвать null(для какого типа следует использовать null?).


2
Однако в классе String нет оператора + .. Так это компилятор переводит на String.Concat?
superlogical

@superlogical Да, это то, что делает компилятор. Так что на самом деле +оператор для строк в C # - это просто синтаксический сахар для String.Concat.
Botz3000

В дополнение к этому: компилятор автоматически выбирает наиболее подходящую перегрузку Concat. Доступные перегрузки: 1, 2 или 3 параметра объекта, 4 параметра объекта + __arglistи paramsверсия массива объектов.
Wormbo

Какой массив строк там модифицируется? ConcatВсегда ли создается новый массив ненулевых строк, даже если задан массив ненулевых строк, или происходит что-то еще?
supercat 09

@supercat Модифицированный массив - это новый массив, который затем передается частному вспомогательному методу. Вы можете сами посмотреть код с помощью рефлектора.
Botz3000

29

Первый образец будет переведен на:

var bob = String.Concat("abc123", null, null, null, "abs123");

ConcatВходной метод проверки и перевести нуль как пустая строка

Второй образец будет переведен на:

var wtf = ((object)null).ToString();

Таким образом, здесь nullбудет сгенерировано ссылочное исключение


Собственно, AccessViolationExceptionбудет брошено :)
леппи

Я только что сделал :) ((object)null).ToString()=> AccessViolation ((object)1 as string).ToString()=>NullReference
leppie

1
У меня есть исключение NullReferenceException. DN 4.0
Вячеслав Смитюх

Интересный. Когда я вставляю ((object)null).ToString()внутрь, try/catchя NullReferenceExceptionтоже получаю .
leppie

3
@Ieppie, за исключением того, что он тоже не генерирует AccessViolation (зачем?) - NullReferenceException здесь.
jeroenh

11

Первая часть вашего кода обрабатывается так же, как в String.Concat,

это то, что компилятор C # вызывает при добавлении строк. " abc" + nullпереводится на String.Concat("abc", null),

а внутри этот метод заменяется nullна String.Empty. Вот почему ваша первая часть кода не вызывает никаких исключений. это просто как

var bob = "abc" + string.Empty + string.Empty + string.Empty + "123";  //abc123

И во 2-й части вашего кода выдается исключение, потому что «null» не является объектом, ключевое слово null - это литерал, представляющий пустую ссылку , которая не ссылается ни на какой объект. null - это значение по умолчанию для переменных ссылочного типа.

И " ToString()" - это метод, который может быть вызван экземпляром объекта, но не любым литералом.


3
String.Emptyне равно null. Это просто обрабатывается таким же образом в некоторых методах, таких как String.Concat. Например, если у вас установлена ​​строковая переменная null, C # не заменит ее, String.Emptyесли вы попытаетесь вызвать для нее метод.
Botz3000

3
Почти. nullне обрабатывается так, как String.EmptyC #, он просто обрабатывается так же, как это происходит в String.Concatкомпиляторе C #, когда вы добавляете строки. "abc" + nullпреобразуется в String.Concat("abc", null), и внутренне этот метод заменяется nullна String.Empty. Вторая часть полностью верна.
Botz3000

9

В структуре COM, предшествовавшей .NET, для любой подпрограммы, получившей строку, было необходимо освободить ее, когда она будет с ней выполнена. Поскольку пустые строки очень часто передавались в подпрограммы и из них, и поскольку попытка «освободить» нулевой указатель была определена как законная операция, не выполняющая никаких действий, Microsoft решила, что указатель нулевой строки представляет собой пустую строку.

Чтобы обеспечить некоторую совместимость с COM, многие подпрограммы в .net будут интерпретировать нулевой объект как законное представление как пустую строку. С парой небольших изменений .net и его языков (в первую очередь, позволяя членам экземпляра указывать «не вызывать как виртуальный»), Microsoft могла бы заставить nullобъекты объявленного типа String вести себя даже больше как пустые строки. Если бы Microsoft сделала это, ей также пришлось бы Nullable<T>работать несколько иначе (чтобы разрешить - Nullable<String>то, что они должны были сделать в любом случае, ИМХО) и / или определить NullableStringтип, который был бы в основном взаимозаменяемым String, но который не учитывал бы nullкак допустимая пустая строка.

Как бы то ни было, есть некоторые контексты, в которых a nullбудет рассматриваться как допустимая пустая строка, а в других - нет. Не очень полезная ситуация, но программисты должны знать о ней. Как правило, выражения формы stringValue.someMemberне работают, если они stringValueесть null, но большинство методов и операторов инфраструктуры, которые принимают строки в качестве параметров, будут рассматриваться nullкак пустая строка.


5

'+'- инфиксный оператор. Как и любой оператор, он действительно вызывает метод. Вы можете представить себе неинфиксную версию"wow".Plus(null) == "wow"

Примерно так решил исполнитель ...

class String
{
  ...
  String Plus(ending)
  {
     if(ending == null) return this;
     ...
  }
} 

Итак .. ваш пример становится

var bob = "abc".Plus(null).Plus(null).Plus(null).Plus("123");  // abc123

который совпадает с

var bob = "abc".Plus("123");  // abc123

Ни в коем случае значение null не становится строкой. Так null.ToString()что ничем не отличается null.VoteMyAnswer(). ;)


На самом деле это больше похоже var bob = Plus(Plus(Plus(Plus("abc",null),null),null),"123");. В основе перегрузки операторов лежат статические методы: msdn.microsoft.com/en-us/library/s53ehcz3(v=VS.71).aspx Если бы они не были var bob = null + "abc";или были string bob = null + null;бы недопустимыми.
Бен Мошер

Вы правы, я просто почувствовал, что буду слишком запутан, если объясню это так.
Найджел Торн

3

Я думаю, потому что это литерал, который не относится ни к какому объекту. ToString()нужен object.


3

Кто-то сказал в этой ветке обсуждения, что вы не можете сделать строку из ничего. (что, на мой взгляд, хорошая фраза). Но да - вы можете :-), как показывает следующий пример:

var x = null + (string)null;     
var wtf = x.ToString();

отлично работает и вообще не вызывает исключения. Единственное отличие состоит в том, что вам нужно преобразовать один из нулей в строку - если вы удалите приведение (string) , то пример все равно компилируется, но выдает исключение времени выполнения: «Оператор '+' неоднозначен для операндов введите '<null>' и '<null>' ".

NB. В приведенном выше примере кода значение x не равно нулю, как вы могли ожидать, это фактически пустая строка после преобразования одного из операндов в строку.


Другой интересный факт заключается в том, что в C # / .NET способ nullобработки не всегда одинаков, если рассматривать разные типы данных. Например:

int? x = 1;  //  string x = "1";
x = x + null + null;
Console.WriteLine((x==null) ? "<null>" : x.ToString());

Что касается 1-й строки фрагмента кода: если xэто целочисленная переменная, допускающая значение NULL (т. Е. int?), Содержащая значение 1, то вы получаете результат <null>обратно. Если это строка (как показано в комментарии) со значением "1", то вы "1"скорее возвращаетесь, чем <null>.

NB Также интересно: если вы используете var x = 1;для первой строки, вы получаете ошибку времени выполнения. Зачем? Поскольку присвоение превратит переменную xв тип данных int, который не допускает значения NULL. Компилятор здесь не предполагает int?, и, следовательно, не работает во второй строке, где nullдобавлено.


2

Добавление nullв строку просто игнорируется. null(в вашем втором примере) не является экземпляром какого-либо объекта, поэтому у него даже нет ToString()метода. Это просто буквально.


1

Потому что нет разницы между string.Emptyи, nullкогда вы объединяете строки. Вы также можете передать null string.Format. Но вы пытаетесь вызвать метод null, который всегда приводит к NullReferenceExceptionвозникновению ошибки компилятора.
Если по какой-то причине вы действительно хотите это сделать, вы можете написать метод расширения, который проверяет, nullа затем возвращает string.Empty. Но такое расширение следует использовать только в случае крайней необходимости (на мой взгляд).


0

В общем: это может быть или не быть допустимым, принимая значение null в качестве параметра в зависимости от спецификации, но всегда недопустимо вызывать метод для null.


Это и другая тема, почему операнды оператора + могут быть нулевыми в случае строк. Это что-то вроде VB (извините, ребята), чтобы облегчить жизнь программистам, или предположим, что программист не может иметь дело с нулями. Я полностью не согласен с этой спецификацией. "unknown" + "something" должно оставаться "unknown" ...

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.