Почему .NET String неизменяема?

Как мы все знаем, String является неизменным. Каковы причины неизменяемости String и введение класса StringBuilder как изменяемого?

Ответ 1

  • Экземпляры неизменяемых типов по своей сути являются потокобезопасными, так как нить не может его модифицировать, риск изменения потока, изменяющего его таким образом, который мешает другому, удаляется (сама ссылка - это другое дело).
  • Точно так же тот факт, что сглаживание не может произвести изменения (если x и y оба относятся к одному и тому же объекту, изменение в x влечет за собой изменение y) позволяет значительно оптимизировать компиляцию.
  • Оптимизация памяти также возможна. Интернационализация и атомизация являются наиболее очевидными примерами, хотя мы можем делать другие версии одного и того же принципа. Я когда-то производил сохранение памяти примерно на половину ГБ, сравнивая неизменяемые объекты и заменяя ссылки на дубликаты, чтобы все они указывали на один и тот же экземпляр (отнимающий много времени, но на минуту дополнительный запуск для сохранения огромного объема памяти был выигрыш производительности в рассматриваемом случае). С изменчивыми объектами, которые не могут быть выполнены.
  • Никакие побочные эффекты не могут возникнуть при передаче неизменяемого типа в качестве метода к параметру, если он не является out или ref (поскольку это изменяет ссылку, а не объект). Поэтому программист знает, что если string x = "abc" в начале метода и не изменяется в теле метода, то x == "abc" в конце метода.
  • Концептуально семантика больше похожа на типы значений; в частности, равенство основано скорее на государстве, чем на идентичности. Это означает, что "abc" == "ab" + "c". Хотя это не требует неизменности, тот факт, что ссылка на такую ​​строку всегда будет равна "abc" на протяжении всего ее жизненного цикла (что требует неизменности) делает использование в качестве ключей, где сохранение равенства с предыдущими значениями жизненно важно, гораздо проще обеспечить правильность (строки обычно используются в качестве ключей).
  • Концептуально, может быть разумнее быть неизменным. Если мы добавим месяц на Рождество, мы не изменили Рождество, мы выпустили новую дату в конце января. Поэтому имеет смысл, что Christmas.AddMonths(1) создает новый DateTime вместо изменения изменчивого. (Другой пример: если я как изменчивый объект меняю свое имя, изменилось имя, которое я использую, "Джон" остается неизменным, а другие Jons не будут затронуты.
  • Копирование выполняется быстро и просто, чтобы создать клон только return this. Поскольку копия не может быть изменена в любом случае, притворяясь, что ее собственная копия безопасна.
  • [Править, я забыл об этом]. Внутреннее состояние можно безопасно распределять между объектами. Например, если вы выполняли список, который поддерживался массивом, индекс начала и подсчет, то самой дорогостоящей частью создания субдиапазона было бы копирование объектов. Однако, если он был неизменным, объект субдиапазона мог ссылаться на один и тот же массив, только с начальным индексом и счетчиком, с существенным изменением времени очень.

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

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

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

Ответ 2

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

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

Ответ 3

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

Если вы не верите, что я смотрю на определение String.Concat, используя reflector. Последние строки...

int length = str0.Length;
string dest = FastAllocateString(length + str1.Length);
FillStringChecked(dest, 0, str0);
FillStringChecked(dest, length, str1);
return dest;

Как вы можете видеть, FastAllocateString возвращает пустую, но выделенную строку, а затем ее изменяет на FillStringChecked

На самом деле FastAllocateString является extern-методом, а FillStringChecked является небезопасным, поэтому он использует указатели для копирования байтов.

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

Ответ 4

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

Ответ 5

Почему типы строк неизменяемы в С#

String - ссылочный тип, поэтому он никогда не копируется, а передается по ссылке. Сравните это с С++ std::stringобъект (который не является неизменным), который передается по значению. Это означает, что если вы хотите использовать String в качестве ключа в Hashtable, вы в курсе на С++, потому что С++ скопирует строку для хранения ключ в хэш-таблице (на самом деле std:: hash_map, но все же) для последующего сравнение. Поэтому, даже если вы позже измените экземпляр std::stringты в порядке. Но в .Net, когда вы используете Строка в Hashtable, она будет хранить ссылка на этот экземпляр. Теперь предположим на мгновение, что строки не являются неизменными и видят, что бывает: 1. Кто-то вставляет значение х с ключом "привет" в Hashtable. 2. Hashtable вычисляет значение хеша для строки и помещает ссылка на строку и значение x в соответствующем ковше. 3. Пользователь модифицирует экземпляр String как "до свидания". 4. Теперь кто-то хочет значение в хеш-таблице, связанную с "привет". Это заканчивается тем, что смотрит в правильное ведро, но при сравнении строк это говорит "bye"!= "привет", поэтому никакое значение не является вернулся. 5. Может быть, кто-то хочет значение "до свидания"? "пока", возможно, имеет хэш, поэтому хэш-таблица будет выглядеть в другой ковш. Нет "бай" в это ведро, поэтому наша запись все еще не найдено.

Создание неизменяемых строк означает, что шаг 3 невозможно. Если кто-то изменяет строку, которую он создает новый объект строки, оставив старый в одиночестве. Это означает, что ключ в hashtable по-прежнему "привет", и, таким образом, все еще правильно.

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

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

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

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

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

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

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

Ответ 10

Представьте, что вы передаете изменяемую строку функции, но не ожидаете ее изменения. Тогда что, если функция изменит эту строку? Например, в С++ вы можете просто выполнять посылку (разницу между параметрами std::string и std::string&), но в С# все это касается ссылок, поэтому, если вы передали изменяемые строки вокруг каждой функции, это могло бы изменить ее и вызвать неожиданное побочные эффекты.

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

Ответ 11

Существует пять общих способов хранения данных хранилища данных класса, которые не могут быть изменены за пределами элемента управления хранилища:

  1. В качестве примитивов типа значения
  2. Сохраняя свободно распространяемую ссылку на объект класса, свойства которого являются неизменными
  3. Сохраняя ссылку на объект изменяемого класса, который никогда не будет подвергаться действию, который может исказить любые интересующие свойства
  4. Как структура, "изменяемая" или "неизменяемая", все поля которой имеют типы # 1- # 4 (не # 5).
  5. Сохраняя только оставшуюся копию ссылки на объект, свойства которого могут быть изменены только через эту ссылку.

    Поскольку строки имеют переменную длину, они не могут быть примитивами типа значений, а также не могут храниться их символьные данные в структуре. Среди оставшихся вариантов единственное, что не требовало бы, чтобы данные символов строк сохранялись в каком-то неизменяемом объекте, было бы # 5. Хотя можно было бы разработать структуру вокруг варианта № 5, этот выбор потребовал бы, чтобы любой код, который хотел бы получить копию строки, которая не могла быть изменена вне ее контроля, должна была бы сделать личную копию для себя. Хотя вряд ли это невозможно сделать, количество дополнительного кода, необходимого для этого, и объем дополнительной обработки во время выполнения, необходимый для создания защитных копий всего, значительно перевесят небольшие преимущества, которые могут возникнуть в результате string может быть изменен, особенно, учитывая, что существует изменяемый тип строки (System.Text.StringBuilder), который выполняет 99% того, что может быть выполнено с помощью изменяемого string.

Ответ 12

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

Ответ 13

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