Вопрос

В .NET строки являются неизменяемыми и являются переменными ссылочного типа.Это часто становится неожиданностью для новых разработчиков .NET, которые могут ошибочно принять их за объекты типа значения из-за их поведения.Однако, помимо практики использования StringBuilder для длительной конкатенации, особенно.в циклах, есть ли какая-либо причина на практике, по которой нужно знать это различие?

Каким сценариям реального мира можно помочь или избежать, понимая различие между ссылками на значения в отношении строк .NET ипросто притворяетесь / неправильно понимаете, что это типы значений?

Это было полезно?

Решение

Конструкция strings был намеренно создан таким образом, чтобы вам, как программисту, не нужно было слишком сильно беспокоиться об этом.Во многих ситуациях это означает, что вы можете просто назначать, перемещать, копировать, изменять строки, не слишком задумываясь о возможных сложных последствиях, если бы другая ссылка на вашу строку существовала и была бы изменена одновременно (как это происходит со ссылками на объекты).

Строковые параметры в вызове метода

(РЕДАКТИРОВАТЬ:этот раздел добавлен позже)
Когда строки передаются методу, они передаются по ссылке.Когда они считываются только в теле метода, ничего особенного не происходит.Но когда они изменяются, создается копия, и временная переменная используется в остальной части метода.Этот процесс называется копирование при записи.

Что беспокоит юниоров, так это то, что они привыкли к тому факту, что объекты являются ссылками и они изменяются в методе, который изменяет переданный параметр.Чтобы сделать то же самое со строками, им необходимо использовать ref ключевое слово.Это фактически позволяет изменять ссылку на строку и возвращать ее вызывающей функции.Если вы этого не сделаете, строка не может быть изменена телом метода:

void ChangeBad(string s)      { s = "hello world"; }
void ChangeGood(ref string s) { s = "hello world"; }

// in calling method:
string s1 = "hi";
ChangeBad(s1);       // s1 remains "hi" on return, this is often confusing
ChangeGood(ref s1);  // s1 changes to "hello world" on return

В StringBuilder

Это различие важно, но начинающим программистам обычно лучше не знать о нем слишком много.Используя StringBuilder когда вы выполняете много строк, "построение" - это хорошо, но часто вашему приложению приходится жарить гораздо больше рыбы, а прирост производительности невелик. StringBuilder ничтожно мала.Будьте осторожны с программистами, которые говорят вам это ВСЕ манипулирование строками должно выполняться с помощью StringBuilder.

Как очень грубое эмпирическое правило:StringBuilder требует определенных затрат на создание, но добавление обходится дешево.Стоимость создания String невелика, но конкатенация обходится относительно дорого. Поворотный момент составляет около 400-500 конкатенаций, в зависимости от размера:после этого StringBuilder становится более эффективным.

Подробнее о StringBuilder и производительности string

Редактировать:основываясь на комментарии Конрада Рудольфа, я добавил этот раздел.

Если предыдущее эмпирическое правило вызывает у вас недоумение, рассмотрите следующие несколько более подробные объяснения:

  • StringBuilder с большим количеством добавлений небольших строк довольно быстро опережает конкатенацию строк (30, 50 добавлений), но за 2 мкс даже 100% прирост производительности часто незначителен (безопасен для некоторых редких ситуаций);
  • StringBuilder с добавлением нескольких больших строк (80 символов или строк большего размера) опережает конкатенацию строк только после тысяч, иногда сотых долей тысяч итераций, и разница часто составляет всего несколько процентов;
  • Смешивание действий со строками (замена, вставка, подстрока, регулярное выражение и т.д.) Часто делает использование StringBuilder или конкатенацию строк равными;
  • Конкатенация строк констант может быть оптимизирована компилятором, CLR или JIT, но не для StringBuilder;
  • Код часто смешивает конкатенацию +, StringBuilder.Append, String.Format, ToString и другие строковые операции, использование StringBuilder в таких случаях вряд ли когда-либо эффективно.

Итак, когда является это эффективно?В случаях, когда добавляется много маленьких строк, например, для сериализации данных в файл, и когда вам не нужно изменять "записанные" данные после того, как они были "записаны" в StringBuilder.И в тех случаях, когда многим методам нужно что-то добавить, потому что StringBuilder является ссылочным типом, и строки копируются при их изменении.

На интернированных строках

Проблема возникает — и не только у младших программистов, — когда они пытаются провести сравнение ссылок и обнаруживают, что иногда результат является истинным, а иногда ложным, казалось бы, в одних и тех же ситуациях.Что случилось?Когда строки были интернированы компилятором и добавлены в глобальный статический интернированный пул строк, сравнение между двумя строками может указывать на один и тот же адрес памяти.Когда (ссылка!) сравнение двух равных строк, одной интернированной, а другой нет, выдаст false.Использование = сравнение, или Equals и не стоит шутить с ReferenceEquals когда имеешь дело со строками.

В строке.Пусто

В эту же лигу вписывается странное поведение, которое иногда возникает при использовании String.Empty:статический String.Empty всегда интернирована, но переменная с присвоенным значением - нет.Однако по умолчанию компилятор назначает String.Empty и укажите на тот же адрес памяти.Результат:изменяемая строковая переменная, если сравнивать с ReferenceEquals, возвращает true , в то время как вместо этого вы могли бы ожидать false.

// emptiness is treated differently:
string empty1 = String.Empty;
string empty2 = "";
string nonEmpty1 = "something";
string nonEmpty2 = "something";

// yields false (debug) true (release)
bool compareNonEmpty = object.ReferenceEquals(nonEmpty1, nonEmpty2);

// yields true (debug) false (release, depends on .NET version and how it's assigned)
bool compareEmpty = object.ReferenceEquals(empty1, empty2);

В глубине

Вы в основном спрашивали о том, какие ситуации могут возникнуть у непосвященных.Я думаю, моя точка зрения сводится к тому, чтобы избегать object.ReferenceEquals потому что ему нельзя доверять при использовании со строками.Причина в том, что интернирование строки используется, когда строка является постоянной в коде, но не всегда.Вы не можете полагаться на такое поведение.Хотя String.Empty и "" всегда интернируются, но не тогда, когда компилятор считает значение изменяемым.Различные варианты оптимизации (debug vs release и другие) приведут к разным результатам.

Когда делай вам нужно ReferenceEquals в любом случае?С объектами это имеет смысл, но со строками - нет.Научите любого, кто работает со строками, избегать их использования, если только они сами не понимают unsafe и прикрепленные предметы.

Производительность

Когда важна производительность, вы можете обнаружить, что строки на самом деле не неизменный и что используя StringBuilder является не всегда самый быстрый подход.

Большая часть информации, которую я использовал здесь, является подробной в этой замечательной статье о струнах, а также "как" для манипулирования строкой на месте (изменяемые строки).

Обновить: добавлен пример кода
Обновить: добавлен раздел "подробно" (надеюсь, кто-нибудь найдет это полезным ;)
Обновить: добавлено несколько ссылок, добавлен раздел о строковых параметрах
Обновить: добавлена оценка того, когда следует переключаться со strings на stringbuilder
Обновить: добавлен дополнительный раздел о StringBuilder vs String performance после замечания Конрада Рудольфа

Другие советы

Единственное различие, которое действительно имеет значение для большинства кодов, - это тот факт, что null может быть присвоен строковым переменным.

Неизменяемый класс действует как тип значения во всех распространенных ситуациях, и вы можете выполнять довольно много программ, не особо заботясь о разнице.

Именно тогда, когда вы копаете немного глубже и заботитесь о производительности, вы получаете реальную пользу от этого различия.Например, знать, что, хотя передача строки в качестве параметра методу действует так, как будто создается копия строки, на самом деле копирование не происходит.Это может быть сюрпризом для людей, привыкших к языкам, где строки на самом деле являются типами значений (например, VB6?), и передача большого количества строк в качестве параметров не пойдет на пользу производительности.

Струна - особая порода.Они являются ссылочным типом, но используются большинством программистов в качестве типа значения.Делая его неизменяемым и используя интерн-пул, он оптимизирует использование памяти, которое будет огромным, если это чистый тип значения.

Подробнее читайте здесь:
Строковый объект C # .NET действительно является ссылкой?на ТАКОМ
Строка.Метод стажировки в MSDN
строка (ссылка на C #) в MSDN

Обновить:
Пожалуйста, обратитесь к abelкомментарий к этому сообщению.Это исправило мое вводящее в заблуждение утверждение.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top