Строки неизменяемы
Напечатанный чек нельзя исправить. Если цену пробили неверно, кассир не стирает цифры, а печатает новый чек. Старый при этом не меняется: его можно выбросить, а можно оставить себе.
Строки в C# неизменяемы: после создания ни один символ строки поменять нельзя. Методы вроде ToUpper не правят строку, а создают новую, как кассир печатает новый чек. Что будет, если про новую строку забыть?
string name = "anna";
name.ToUpper();
Console.WriteLine(name); // anna
name = name.ToUpper();
Console.WriteLine(name); // ANNAДавайте разберём пример по строкам:
name.ToUpper();создаёт новую строку"ANNA", но её никуда не сохранили, и она пропадает.Console.WriteLine(name)печатаетanna: исходная строка осталась прежней.name = name.ToUpper();снова создаёт"ANNA"и на этот раз кладёт её в переменнуюname.- Теперь печатается
ANNA.
Неизменяема строка, а не переменная. Переменной name можно присвоить новую строку сколько угодно раз. Нельзя только поменять символы внутри строки, которая уже создана.
Метод возвращает новую строку
Каждый метод, который будто бы меняет строку, создаёт новую и возвращает её. Так работают ToUpper, ToLower, Trim, Replace и Substring. Чтобы результат не пропал, его присваивают: в новую переменную или в ту же самую.
Компилятор не предупреждает, когда результат вызова пропадает: строка name.ToUpper(); компилируется без единого предупреждения. Поэтому такая ошибка часто остаётся незамеченной.
В виджете переменная строки, как и переменная массива, показана стрелкой: → "cat" значит «ведёт к строке cat». Нажимайте «Следующий шаг» и следите, к какой строке ведёт каждое имя после ToUpper и после +=.
1string a = "cat";2string b = a;3a.ToUpper();4Console.WriteLine(a);5string c = a.ToUpper();6Console.WriteLine(c);7a += "s";8Console.WriteLine(a);9Console.WriteLine(b);
Создаётся строка cat, и в a кладётся ссылка на неё.
ToUpper ни разу не изменил строку "cat": первый раз его результат пропал, второй раз попал в c. И += не дописал букву в старую строку, а создал новую строку "cats" и перевёл на неё a.
Две переменные — одна строка
Как и переменная массива, переменная типа string хранит ссылку: string — ссылочный тип. Поэтому string b = a; копирует ссылку, и у одной строки становится два имени.
С массивом запись через b изменила бы общий массив, и это было бы видно через a. Со строкой так не выйдет: ни один метод не правит строку. Любое «изменение» даёт новую строку, и к ней переходит только та переменная, которой её присвоили.
Здесь чек и строка расходятся. Бумажный чек лежит в одной руке, а к одной строке могут вести сколько угодно переменных. Это безопасно как раз потому, что строку никто не исправит.
Зачем строки неизменяемы
Этот вопрос часто задают на собеседовании: почему string — ссылочный тип, но ведёт себя как значение? Потому что строку нельзя изменить: общая ссылка на неё ничем не хуже собственной копии. Неизменяемость даёт две главные выгоды:
- строку можно передать в метод или сохранить в нескольких переменных, и никто не изменит её у вас за спиной;
- одинаковые литералы, например два
"cat"в одной программе, среда выполнения хранит как одну строку, и это экономит память.
У неизменяемости есть цена. Каждое «изменение» создаёт новую строку и копирует в неё символы. Одна такая копия незаметна, а тысячи копий в цикле уже замедляют программу.
Частая ошибка
Символ строки попробовали заменить по индексу:
string word = "cat";
word[0] = 'b'; // ✗
Console.WriteLine(word);error CS0200: Property or indexer 'string.this[int]' cannot be assigned to -- it is read onlyПрограмма не компилируется. Квадратные скобки у строки работают только на чтение: word[0] можно прочитать, но не записать. Слово indexer в сообщении означает как раз доступ по индексу в квадратных скобках. Чтобы получить bat, соберите новую строку: word = "b" + word.Substring(1);.
Главное
- Строка неизменяема: после создания её символы поменять нельзя, и
s[0] = 'x'не компилируется. - Методы строки возвращают новую строку, поэтому
s.ToUpper();без присваивания ничего не меняет. - Неизменяема строка, а не переменная:
s = s.ToUpper()кладёт вsновую строку. b = aдаёт строке второе имя, ноa += "s"создаёт новую строку, иbпо-прежнему ведёт к старой.