Почему Строка.NET неизменна? [дубликат]

Этот вопрос уже имеет ответ здесь:

Как все мы знаем, Строка неизменна. Каковы причины Строки, являющейся неизменным и введение класса StringBuilder как изменяемые?

182
задан user 3 March 2012 в 11:10
поделиться

12 ответов

  1. Экземпляры неизменяемых типов по своей сути являются потокобезопасными, поскольку ни один поток не может их модифицировать, риск того, что поток изменит его таким образом, который будет мешать другому, устранен (сама ссылка - это другое дело).
  2. Точно так же тот факт, что псевдонимы не могут производить изменения (если x и y относятся к одному и тому же объекту, изменение x влечет за собой изменение y), позволяет значительно оптимизировать компилятор.
  3. Также возможна оптимизация с экономией памяти. Наиболее очевидные примеры - интернирование и атомизация, хотя мы можем использовать и другие версии того же принципа. Однажды я добился экономии памяти примерно на половину ГБ, сравнив неизменяемые объекты и заменив ссылки на дубликаты, чтобы все они указывали на один и тот же экземпляр (отнимает много времени, но дополнительная минута запуска для экономии огромного объема памяти была непростой задачей. выигрыш производительности в рассматриваемом случае). С изменяемыми объектами этого сделать нельзя.
  4. Никакие побочные эффекты не могут возникнуть при передаче неизменяемого типа в качестве метода параметру, если только он не out или ref (поскольку это изменяет ссылку, а не объект). Таким образом, программист знает, что если строка x = "abc" в начале метода, и это не изменяется в теле метода, то x == "abc" в конце метода.
  5. Концептуально семантика больше похожа на типы значений; в частности, равенство основывается на государстве, а не на идентичности. Это означает, что "abc" == "ab" + "c" .Хотя это не требует неизменяемости, тот факт, что ссылка на такую ​​строку всегда будет равняться «abc» на протяжении всего ее жизненного цикла (что требует неизменности), позволяет использовать ее в качестве ключей, где сохранение равенства с предыдущими значениями жизненно важно, гораздо проще обеспечить правильность. of (строки действительно обычно используются в качестве ключей).
  6. С концептуальной точки зрения, может иметь больше смысла быть неизменным. Если мы добавим месяц к Рождеству, мы не изменили Рождество, мы создали новую дату в конце января. Поэтому имеет смысл, что Christmas.AddMonths (1) создает новый DateTime , а не изменяет изменяемый. (Другой пример: если я как изменяемый объект меняю свое имя, изменилось только то, какое имя я использую, «Джон» останется неизменным, и другие Йоны не пострадают.
  7. Копирование выполняется быстро и просто, достаточно просто создать клон верните это . Поскольку копию все равно нельзя изменить, притворяться, что что-то является ее собственной копией, безопасно.
  8. [Edit, я забыл об этом]. Внутреннее состояние можно безопасно разделить между объектами. Например, если вы реализуете список, который поддерживался массивом, начальным индексом и счетчиком, то самой дорогой частью создания поддиапазона было бы копирование объектов. Однако, если он был неизменным, то объект поддиапазона мог ссылаться на тот же массив, с изменением только начального индекса и счетчика, с очень значительным изменением времени построения.

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

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

Копирование при записи - это золотая середина. Здесь «настоящий» класс содержит ссылку на класс «состояния». Классы состояний используются совместно с операциями копирования, но если вы измените состояние, будет создана новая копия класса состояния. Это чаще используется с C ++, чем с C #, поэтому std: string обладает некоторыми, но не всеми преимуществами неизменяемых типов, оставаясь при этом изменяемым.

236
ответ дан 23 November 2019 в 06:05
поделиться

Строки и другие конкретные объекты обычно выражаются как неизменяемые объекты для повышения удобочитаемости и эффективности выполнения. Еще одна проблема - безопасность. Процесс не может изменить вашу строку и вставить код в строку

3
ответ дан 23 November 2019 в 06:05
поделиться

Управление строкой - дорогостоящий процесс. сохранение неизменяемости строк позволяет повторно использовать повторяющиеся строки, а не создавать их заново.

14
ответ дан 23 November 2019 в 06:05
поделиться

Неизменяемые строки также предотвращают проблемы, связанные с параллелизмом.

0
ответ дан 23 November 2019 в 06:05
поделиться

Представьте, что вы передаете функции изменяемую строку, но не ожидаете, что она будет изменена. А что, если функция изменит эту строку? В C++, например, вы могли бы просто сделать call-by-value (разница между std::string и std::string& параметром), но в C# все дело в ссылках, так что если вы передаете мутабельные строки, каждая функция может изменить их и вызвать неожиданные побочные эффекты.

Это лишь одна из различных причин. Еще одна - производительность (например, интернированные строки).

3
ответ дан 23 November 2019 в 06:05
поделиться

В .NET строки передаются как ссылочные типы.

Ссылочные типы помещают указатель на стек, на реальный экземпляр, который находится на управляемой куче. Это отличается от типов значений, которые хранят весь свой экземпляр в стеке.

Когда тип значения передается в качестве параметра, среда выполнения создает копию значения на стеке и передает это значение в метод. Вот почему целые числа должны передаваться с ключевым словом 'ref', чтобы вернуть обновленное значение.

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

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

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

5
ответ дан 23 November 2019 в 06:05
поделиться

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

5
ответ дан 23 November 2019 в 06:05
поделиться

Почему строковые типы неизменяемы в 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", и поэтому все еще правильный.

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

13
ответ дан 23 November 2019 в 06:05
поделиться

Просто чтобы добавить это, часто забываемый взгляд на безопасность, представьте этот сценарий, если бы строки были изменяемыми:

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;
}

Вы видите, как это может быть очень, очень плохо, если бы вам разрешили изменять строки после их передачи.

6
ответ дан 23 November 2019 в 06:05
поделиться

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

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

74
ответ дан 23 November 2019 в 06:05
поделиться

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

Если вы мне не верите, посмотрите определение 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 небезопасен, поэтому для копирования байтов он использует указатели.

Может быть, есть лучшие примеры, но это тот, который я нашел.

21
ответ дан 23 November 2019 в 06:05
поделиться

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

0
ответ дан 23 November 2019 в 06:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: