Чому дійсно об'єднувати нульові рядки, але не називати "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 # Reference) - ключове слово null - це буквальне значення, яке представляє нульову посилання, яке не відноситься до жодного об'єкта. null - значення за замовчуванням змінних типу опорного типу.


1
Це працювало б тоді? var wtf = ((String)null).ToString();Я нещодавно працюю в Java, де можливе введення нуля, минуло деякий час, оскільки я працював із C #.
Йохім

14
@Jochem: Ви все ще намагаєтесь викликати метод на нульовому об'єкті, тому я здогадуюсь, що ні. Подумайте про це як null.ToString()проти ToString(null).
Свиш

@Svish так, тепер я про це знову думаю, це нульовий об’єкт, тож ти маєш рацію, він не спрацює. Ні в Java не буде: виключення з нульовим вказівником. Не звертай уваги. Tnx для вашої відповіді! [редагувати: перевірено на Java: NullPointerException. З тією різницею, що з cast він компілює, без cast - не].
Йохім

як правило, немає ніяких причин навмисно конкретизувати або ToString нульове ключове слово. Якщо вам потрібен порожній рядок, використовуйте string.Empty. якщо вам потрібно перевірити, чи є змінною рядка null, ви можете використовувати (myString == null) або string.IsNullOrEmpty (myString). Альтернативно, щоб перетворити нульову змінну рядка в рядок. Порожнє використання 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 Так, це робить компілятор. Тож насправді +оператор на рядках в C # - це просто синтаксичний цукор для String.Concat.
Botz3000

Додавши до цього: компілятор автоматично вибирає перевантаження Concat, що має найбільш сенс. Доступні перевантаження: 1, 2 або 3 параметри об'єкта, 4 параметри об'єкта + __arglistта paramsверсія масиву об'єктів.
Вормбо

Який масив рядків змінюється там? Чи Concatзавжди створюється новий масив ненульових рядків, навіть коли йому надається масив ненульових рядків, або відбувається щось інше?
supercat

@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
леппі

1
У мене є NullReferenceException. DN 4.0
В’ячеслав Смітюх

Цікаво. Коли я поміщаю ((object)null).ToString()всередину, try/catchя NullReferenceExceptionтеж отримую .
леппі

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

І у другій частині вашого коду викидається виняток, оскільки "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>те, що вони повинні зробити IMHO так чи інакше) та / або визначити NullableStringтип, який був би здебільшого взаємозамінним String, але який би не стосувавсяnull як дійсний порожній рядок.

Як це є, є деякі контексти, в яких засіб nullбуде розглядатися як законний порожній рядок та інші, в яких воно не буде. Не дуже корисна ситуація, але така, про яку програмісти повинні знати. Взагалі, вирази форми stringValue.someMemberвиходять з ладу, якщо stringValueє null, але більшість базових методів і операторів, які приймають рядки як параметри, вважатимуться nullпорожнім рядком.


5

'+'є оператором infix. Як і будь-який оператор, він дійсно викликає метод. Можна уявити неінфіксовану версію"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.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();

працює чудово і зовсім не кидає виняток. Єдина відмінність полягає в тому, що вам потрібно ввести один з нулів у рядок - якщо ви видалите (рядок) каст, то приклад все-таки компілюється, але викидає виняток під час виконання: "Оператор '+" неоднозначний для операндів введіть '<null>' та '<null>' ".

Примітка. У наведеному вище прикладі коду значення x не є нульовим, як ви могли очікувати, це фактично порожній рядок після того, як ви вкинули один із операндів у рядок.


Ще один цікавий факт полягає в тому, що в C # / .NET спосіб nullтрактування не завжди однаковий, якщо враховувати різні типи даних. Наприклад:

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

Щодо 1-го рядка фрагмента коду: Якщо xє нульова ціла змінна (тобто int?), що містить значення 1, то ви отримуєте результат <null>назад. Якщо це рядок (як показано в коментарі) зі значенням "1", то ви отримуєте "1"назад, а не <null>.

NB Також цікаво: якщо ви використовуєте var x = 1;для першого рядка, ви отримуєте помилку виконання. Чому? Тому що призначення перетворить змінну xв тип даних int, який не зводиться до нуля. Тут компілятор не передбачає int?, а значить, не працює у другому рядку, де nullдодано.


2

Додавання nullдо рядка просто ігнорується. null(у вашому другому прикладі) не є примірником жодного об'єкта, тому у нього навіть немає ToString()методу. Це просто буквальне.


1

Тому що немає різниці між string.Emptyі nullколи ви стискаєте рядки. Ви також можете пропустити null string.Format. Але ви намагаєтеся викликати метод увімкнення null, який завжди призведе до NullReferenceExceptionі, отже, генерує помилку компілятора.
Якщо з якоїсь причини ви дійсно хочете це зробити, ви можете написати метод розширення, який перевіряє nullта повертається string.Empty. Але таке розширення слід використовувати лише тоді, коли це абсолютно необхідно (на мою думку).


0

Як загальне: Це може бути, а може і невірно приймати null як параметр залежно від специфікації, але завжди недійсним є виклик методу null.


Це та інша тема, чому операнди оператора + можуть бути нульовими у випадку рядків. Це щось на зразок VB (вибачте, хлопці), щоб полегшити життя програмістів, або припустимо, що програміст не може мати справу з нулями. Я повністю не погоджуюся з цією специфікацією. "невідомо" + "що-небудь" має бути все ще "невідомим" ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.