Дизайн string
был преднамеренно таким, что вам, как программисту, не нужно слишком беспокоиться об этом. Во многих ситуациях это означает, что вы можете просто назначать, перемещать, копировать, изменять строки, не думая слишком много о возможных запутанных последствиях, если бы существовала другая ссылка на вашу строку, и она была бы изменена одновременно (как это происходит со ссылками на объекты).
Строковые параметры в вызове метода
(РЕДАКТИРОВАТЬ: этот раздел добавлен позже)
Когда строки передаются методу, они передаются по ссылке. Когда они читаются только в теле метода, ничего особенного не происходит. Но когда они изменяются, создается копия, и временная переменная используется в оставшейся части метода. Этот процесс называется копирование при записи .
Что мешает младшим, так это то, что они привыкли к тому, что объекты являются ссылками, и они изменяются в методе, который изменяет переданный параметр. Чтобы сделать то же самое со строками, им нужно использовать ключевое слово 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 имеет некоторую стоимость создания, но добавление дешево. Строка имеет дешевую стоимость создания, но объединение относительно дорого. Точка поворота составляет около 400-500 конкатенаций, в зависимости от размера: после этого StringBuilder становится более эффективным.
Подробнее о StringBuilder и производительности строки
РЕДАКТИРОВАТЬ: на основе комментария от Конрада Рудольфа, я добавил этот раздел.
Если предыдущее эмпирическое правило заставляет задуматься, рассмотрите следующие чуть более подробные объяснения:
- StringBuilder со многими небольшими строками добавляет довольно быстрое ускорение конкатенации строк (30, 50 добавлений), но через 2 мкс даже 100% прирост производительности часто незначителен (безопасно в некоторых редких ситуациях);
- StringBuilder с добавлением нескольких больших строк (80 символов или больше строк) опережает конкатенацию строк только после тысяч, иногда сотых тысяч итераций, и разница часто составляет всего несколько процентов;
- Смешивание строковых действий (замена, вставка, подстрока, регулярное выражение и т. Д.) Часто делает использование StringBuilder или конкатенации строк равным;
- Конкатенация строк может быть оптимизирована компилятором, CLR или JIT, для StringBuilder - нет;
- Код часто смешивает конкатенацию
+
, StringBuilder.Append
, String.Format
, ToString
и другие строковые операции, использование StringBuilder в таких случаях едва ли когда-либо эффективно.
Итак, когда является , это эффективно? В тех случаях, когда добавляется много небольших строк, например, для сериализации данных в файл, например, и когда вам не нужно изменять «записанные» данные после «записи» в StringBuilder. И в случаях, когда многим методам нужно что-то добавить, потому что StringBuilder является ссылочным типом, а строки копируются при их изменении.
На интернированных струнах
Возникает проблема & mdash; не только с младшими программистами & mdash; когда они пытаются провести сравнительное сравнение и обнаруживают, что иногда результат верен, а иногда ложен, в, казалось бы, одинаковых ситуациях. Что случилось? Когда строки были обработаны компилятором и добавлены в глобальный статический объединенный пул строк, сравнение между двумя строками может указывать на один и тот же адрес памяти. Когда (ссылка!) Сравнение двух одинаковых строк, одна интернированная, а другая нет, даст false. Используйте =
сравнение или Equals
и не играйте с ReferenceEquals
при работе со строками.
On String.Empty
В той же лиге подходит странное поведение, которое иногда возникает при использовании 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
и ""
всегда интернированы, это не то, когда компилятор считает, что значение можно изменить. Различные варианты оптимизации (отладка или выпуск и другие) будут давать разные результаты.
Когда сделать вам нужно ReferenceEquals
в любом случае? Для объектов это имеет смысл, а для строк - нет. Научите всех, кто работает со строками, избегать их использования, если они не понимают unsafe
и закрепленные объекты.
Performance
Когда важна производительность, вы можете обнаружить, что строки на самом деле не неизменны и что с использованием StringBuilder
означает не всегда самый быстрый подход .
Большая часть информации, которую я здесь использовал, подробно описана в этой превосходной статье о строках , а также "как сделать" для манипулирования строками на месте (изменяемые строки).
Обновление: добавлен пример кода
Обновление: добавлен раздел "в глубину" (надеюсь, кто-то найдет это полезным;)
Обновление: добавлены некоторые ссылки, добавлен раздел о строковых параметрах
Обновление: добавлена оценка времени перехода от строк к строителю строк
Обновление: добавлен дополнительный раздел о производительности StringBuilder и String после замечания Конрада Рудольфа