Дизайн string
s был преднамеренно таким, чтобы вам как программисту не нужно было слишком беспокоиться об этом. Во многих ситуациях это означает, что вы можете просто назначать, перемещать, копировать, изменять строки, не слишком задумываясь о возможных запутанных последствиях, если другая ссылка на вашу строку существует и будет изменена в то же время (как это происходит со ссылками на объекты).
Строковые параметры в вызове метода
(EDIT: этот раздел добавлен позже)
Когда строки передаются методу, они передаются по ссылке. Когда они читаются только в теле метода, ничего особенного не происходит. Но когда они изменяются, создается копия, а временная переменная используется в остальной части метода. Этот процесс называется копирование при записи.
Что беспокоит джуниоров, так это то, что они привыкли к тому, что объекты являются ссылками и они изменяются в методе, который изменяет переданный параметр. Чтобы сделать то же самое со строками, им нужно использовать ключевое слово 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 и строк
EDIT: основываясь на комментарии Конрада Рудольфа, я добавил этот раздел.
Если предыдущее эмпирическое правило заставляет вас задуматься, рассмотрите следующие более подробные объяснения:
- 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
всегда интернируется, а переменная с присвоенным значением — нет. Однако по умолчанию компилятор назначит 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
и закрепленные объекты.
Представление
Когда важна производительность, вы можете обнаружить, что строки на самом деле не являются неизменяемыми и что использование StringBuilder
не всегда является самым быстрым способом.
Большая часть использованной здесь информации подробно описана в этой прекрасной статье о строках. , а также инструкции по работе со строками на месте (изменяемые строки).
Обновление: добавлен образец кода
Обновление: добавлен раздел «Подробно» (надеюсь, кому-то это будет полезно;)
Обновление:< /strong> добавлено несколько ссылок, добавлен раздел по строковым параметрам
Обновление: добавлена оценка того, когда переключаться со строк на конструктор строк
Обновление: добавлен дополнительный раздел о производительности StringBuilder и String после замечания Конрада Рудольфа
person
Abel
schedule
02.11.2009