Этот вопрос уже имеет ответ здесь:
Как все мы знаем, Строка неизменна. Каковы причины Строки, являющейся неизменным и введение класса StringBuilder как изменяемые?
out
или ref
(поскольку это изменяет ссылку, а не объект). Таким образом, программист знает, что если строка x = "abc"
в начале метода, и это не изменяется в теле метода, то x == "abc"
в конце метода. "abc" == "ab" + "c"
.Хотя это не требует неизменяемости, тот факт, что ссылка на такую строку всегда будет равняться «abc» на протяжении всего ее жизненного цикла (что требует неизменности), позволяет использовать ее в качестве ключей, где сохранение равенства с предыдущими значениями жизненно важно, гораздо проще обеспечить правильность. of (строки действительно обычно используются в качестве ключей). Christmas.AddMonths (1)
создает новый DateTime
, а не изменяет изменяемый. (Другой пример: если я как изменяемый объект меняю свое имя, изменилось только то, какое имя я использую, «Джон» останется неизменным, и другие Йоны не пострадают. верните это
. Поскольку копию все равно нельзя изменить, притворяться, что что-то является ее собственной копией, безопасно. В целом, неизменяемость объектов, которые не претерпевают изменений по назначению, может иметь много преимуществ.Главный недостаток состоит в том, что требуются дополнительные конструкции, хотя даже здесь это часто переоценивается (помните, вам нужно сделать несколько добавлений, прежде чем StringBuilder станет более эффективным, чем эквивалентная серия конкатенаций с присущей им конструкцией).
Было бы недостатком, если бы изменчивость была частью цели объекта (кто бы хотел, чтобы его моделировал объект Employee, чья зарплата никогда не может измениться), хотя иногда даже тогда это может быть полезно (во многих веб- и других приложений без сохранения состояния код, выполняющий операции чтения, отделен от кода, выполняющего обновления, и использование других объектов может быть естественным - я бы не стал делать объект неизменяемым, а затем принудительно использовать этот шаблон, но если бы у меня уже был этот шаблон, я мог бы сделать свой " прочтите "объекты неизменяемые для повышения производительности и гарантии правильности").
Копирование при записи - это золотая середина. Здесь «настоящий» класс содержит ссылку на класс «состояния». Классы состояний используются совместно с операциями копирования, но если вы измените состояние, будет создана новая копия класса состояния. Это чаще используется с C ++, чем с C #, поэтому std: string обладает некоторыми, но не всеми преимуществами неизменяемых типов, оставаясь при этом изменяемым.
Строки и другие конкретные объекты обычно выражаются как неизменяемые объекты для повышения удобочитаемости и эффективности выполнения. Еще одна проблема - безопасность. Процесс не может изменить вашу строку и вставить код в строку
Управление строкой - дорогостоящий процесс. сохранение неизменяемости строк позволяет повторно использовать повторяющиеся строки, а не создавать их заново.
Неизменяемые строки также предотвращают проблемы, связанные с параллелизмом.
Представьте, что вы передаете функции изменяемую строку, но не ожидаете, что она будет изменена. А что, если функция изменит эту строку? В C++, например, вы могли бы просто сделать call-by-value (разница между std::string
и std::string&
параметром), но в C# все дело в ссылках, так что если вы передаете мутабельные строки, каждая функция может изменить их и вызвать неожиданные побочные эффекты.
Это лишь одна из различных причин. Еще одна - производительность (например, интернированные строки).
В .NET строки передаются как ссылочные типы.
Ссылочные типы помещают указатель на стек, на реальный экземпляр, который находится на управляемой куче. Это отличается от типов значений, которые хранят весь свой экземпляр в стеке.
Когда тип значения передается в качестве параметра, среда выполнения создает копию значения на стеке и передает это значение в метод. Вот почему целые числа должны передаваться с ключевым словом 'ref', чтобы вернуть обновленное значение.
Когда передается ссылочный тип, среда выполнения создает копию указателя в стеке. Этот скопированный указатель по-прежнему указывает на исходный экземпляр ссылочного типа.
Строковый тип имеет перегруженный оператор =, который создает копию самого себя, а не копию указателя, что делает его поведение более похожим на тип значения. Однако, если бы копировался только указатель, вторая операция со строкой могла бы случайно перезаписать значение частного члена другого класса, что привело бы к довольно неприятным результатам.
Как уже упоминалось в других сообщениях, класс StringBuilder позволяет создавать строки без накладных расходов на GC.
Вам никогда не придется защитно копировать неизменяемые данные. Несмотря на то, что для мутации данных необходимо их копировать, часто возможность свободно псевдонизировать и не беспокоиться о непредвиденных последствиях такого псевдонизирования может привести к повышению производительности из-за отсутствия защитного копирования.
Почему строковые типы неизменяемы в C#
Строка - ссылочный тип, поэтому она никогда не копируется, а передается по ссылке. Сравните это с объектом C++ std::string объектом (который не является неизменяемым), который передается по значению. Это означает, что если вы хотите использовать строку в качестве ключа в Hashtable, вы можете использовать ее в C++, потому что C++ скопирует строку для хранения ключ в хэш-таблице (на самом деле std::hash_map, но все же) для последующего сравнения. Поэтому даже если вы позже измените экземпляр std::string, все будет в порядке. Но в .Net, когда вы используете строку в таблице Hashtable, она будет хранить ссылку на этот экземпляр. Теперь предположим на мгновение, что строки не являются неизменяемыми, и посмотрим, что произойдет: 1. Кто-то вставляет значение x с ключом "hello" в Hashtable. 2. Hashtable вычисляет хэш-значение для String и помещает ссылку на строку и значение в Hashtable. ссылку на строку и значение x в соответствующее ведро. 3. Пользователь изменяет экземпляр String на "пока". 4. Теперь кто-то хочет получить значение в hashtable, связанное с "hello". Он в конечном итоге ищет в правильной корзине, но при сравнении строк он говорит "пока"!="привет", поэтому значение не не возвращается. 5. Может быть, кому-то нужно значение "bye"? "Пока", вероятно, имеет другой хэш, поэтому хэш-таблица будет искать в другом ведре. Ключей "bye" нет в в этом ведре, поэтому наша запись все еще не найдена.
Сделать строки неизменяемыми означает, что шаг 3 невозможен. Если кто-то изменяет строку, он создает новый объект строки, оставляя старый в покое. Это означает, что ключ в хэш-таблице все еще "hello", и поэтому все еще правильный.
Итак, возможно, помимо всего прочего, неизменяемые строки - это способ сделать возможными строки, которые передаются по ссылке быть использованы в качестве ключей в хэш-таблице или подобном словарном объекте.
Просто чтобы добавить это, часто забываемый взгляд на безопасность, представьте этот сценарий, если бы строки были изменяемыми:
string dir = "C:\SomePlainFolder";
//Kick off another thread
GetDirectoryContents(dir);
void GetDirectoryContents(string directory)
{
if(HasAccess(directory) {
//Here the other thread changed the string to "C:\AllYourPasswords\"
return Contents(directory);
}
return null;
}
Вы видите, как это может быть очень, очень плохо, если бы вам разрешили изменять строки после их передачи.
Создание неизменяемых строк имеет много преимуществ. Это обеспечивает автоматическую безопасность потоков и позволяет строкам вести себя как собственный тип простым и эффективным образом. Это также позволяет повысить эффективность во время выполнения (например, обеспечить эффективную интернализацию строк для снижения использования ресурсов) и имеет огромные преимущества в плане безопасности, так как сторонний вызов API не сможет изменить ваши строки.
StringBuilder был добавлен для того, чтобы устранить один существенный недостаток неизменяемых строк - конструирование неизменяемых типов во время выполнения вызывает большое давление на GC и по своей природе является медленным. Создав явный, изменяемый класс для обработки этого, эта проблема решается без добавления ненужных усложнений в класс строк.
Строки на самом деле не являются неизменяемыми. Они просто публично неизменны. Это означает, что вы не можете изменять их из их общедоступного интерфейса. Но внутри они действительно изменчивы.
Если вы мне не верите, посмотрите определение String.Concat
с использованием отражателя .
Последние строки:
int length = str0.Length;
string dest = FastAllocateString(length + str1.Length);
FillStringChecked(dest, 0, str0);
FillStringChecked(dest, length, str1);
return dest;
Как видите, FastAllocateString
возвращает пустую, но выделенную строку, а затем она модифицируется с помощью FillStringChecked
Фактически FastAllocateString
является внешним методом, а FillStringChecked
небезопасен, поэтому для копирования байтов он использует указатели.
Может быть, есть лучшие примеры, но это тот, который я нашел.
Представьте себе ОС, работающую со строкой, которую какой-то другой поток изменение за вашей спиной. Как вы могли проверить что-либо без делать копию?