Ошибка компилятора CS0200 с текстом «невозможно присвоить значение свойству или индексатору string.this[int] — доступ только для чтения» появляется в тот момент, когда код пытается изменить отдельный символ строки напрямую, например через конструкцию вида str[0] = 'a';. Компилятор C# блокирует такую операцию ещё до запуска программы, потому что строки в .NET устроены принципиально иначе, чем массивы символов в C или C++.
Причина кроется не в синтаксисе и не в настройках проекта, а в фундаментальном свойстве типа System.String: строки в .NET являются неизменяемыми (immutable). Индексатор this[int] у строки существует, но у него есть только метод чтения get — метод записи set отсутствует. Поэтому компилятор и сообщает, что присваивание невозможно.
Ниже разберём, почему строки сделаны неизменяемыми, в каких ситуациях разработчики чаще всего сталкиваются с этой ошибкой и какие есть рабочие способы «изменить» строку, не нарушая правил языка.
Почему строки в C# нельзя изменять посимвольно
Тип string в .NET спроектирован как неизменяемый объект. После создания строки её содержимое в памяти не меняется: любая операция, которая «модифицирует» строку (конкатенация, Replace, ToUpper), на самом деле создаёт новый объект в памяти, а старый остаётся нетронутым, пока его не соберёт сборщик мусора.
У такого решения есть веские основания:
- 🔒 Потокобезопасность: одну и ту же строку можно читать из нескольких потоков без блокировок.
- 🗝️ Ключи коллекций: строки можно безопасно использовать как ключи в
Dictionary— их хеш-код никогда не изменится. - ♻️ Интернирование: одинаковые строковые литералы могут ссылаться на один объект в памяти, что экономит ресурсы.
- 🛡️ Защита данных: строку, переданную в метод, нельзя «подменить» изнутри этого метода.
Индексатор string.this[int] предоставляет только доступ на чтение: вы можете получить символ по индексу (char c = str[3];), но не можете записать туда новое значение. Именно это ограничение и формулирует компилятор в сообщении об ошибке.
Строки в .NET неизменяемы: индексатор string.this[int] поддерживает только чтение. Любое «изменение» строки — это создание новой строки.
Как выглядит ошибка в коде
Типичный фрагмент, который вызывает CS0200:
string text = "hello";
text[0] = 'H'; // Ошибка CS0200: доступ только для чтения
Аналогичная ситуация возникает и в циклах, когда разработчик пытается «подправить» строку поэлементно:
string s = "abcdef";
for (int i = 0; i < s.Length; i++)
{
if (s[i] == 'c')
s[i] = 'x'; // CS0200
}
Ещё одна разновидность ошибки — попытка присвоить значение свойству, у которого объявлен только get-аксессор. Компилятор выдаёт тот же код CS0200, но с именем вашего свойства вместо string.this[int]. Это важно различать: если в сообщении фигурирует собственное свойство класса, проблема решается добавлением сеттера или методом инициализации, а не работой со строкой.
⚠️ Внимание: сообщение CS0200 звучит одинаково для индексатора строки и для свойств без сеттера. Всегда смотрите, какое именно имя указано в тексте ошибки — от этого зависит способ исправления.
Способ 1: создать новую строку через замену
Самый простой вариант — не изменять существующую строку, а сформировать новую. Для замены символов и подстрок у класса string есть встроенный метод Replace:
string text = "hello";
text = text.Replace('h', 'H'); // "Hello"
Для замены по конкретной позиции удобно скомбинировать Substring, Remove и Insert:
string s = "abcdef";
s = s.Remove(2, 1).Insert(2, "x"); // "abxdef"
Такой подход хорошо читается и подходит для единичных замен. Но если правок много (например, обработка каждого символа в цикле), постоянное создание новых строк даст лишнюю нагрузку на память — в этом случае лучше перейти к следующим способам.
Методы Remove, Insert, Replace и Substring не меняют исходную строку — они возвращают новую. Не забывайте присваивать результат обратно в переменную: s = s.Replace(...).
Способ 2: массив символов char[]
Если нужно менять символы по индексам многократно, строку можно преобразовать в изменяемый массив char[], выполнить все правки, а затем собрать строку обратно:
string s = "abcdef";
char[] chars = s.ToCharArray();
chars[2] = 'x'; // массив изменять можно
chars[0] = 'A';
s = new string(chars); // "Abxdef"
Массив символов — это обычная изменяемая структура данных, поэтому запись по индексу работает без ограничений. После завершения правок конструктор new string(char[]) создаёт из массива итоговую строку.
Этот способ удобен, когда логика оперирует именно позициями символов: шифрование сдвигом, маскирование части данных, транслитерация по таблице соответствий.
Способ 3: StringBuilder для интенсивных правок
Класс StringBuilder из пространства имён System.Text создан специально для сценариев, где строка собирается или редактируется многократно. В отличие от string, он хранит изменяемый буфер и поддерживает запись по индексу:
using System.Text;
var sb = new StringBuilder("abcdef");
sb[2] = 'x'; // работает, ошибки нет
sb.Append("gh");
string result = sb.ToString(); // "abxdefgh"
Когда стоит выбирать StringBuilder:
- ⚙️ строка собирается в цикле с большим числом итераций;
- ✏️ нужно многократно менять символы по индексам;
- 🧩 текст формируется из множества фрагментов (вставки, удаления, замены).
☑️ Выбор способа обхода ошибки CS0200
Для разового изменения StringBuilder может быть избыточен, но при интенсивной обработке текста он заметно эффективнее, чем создание новой строки на каждой итерации.
Сравнение подходов
| Способ | Изменяемый буфер | Запись по индексу | Когда применять |
|---|---|---|---|
Replace / Insert | Нет (новая строка) | Нет | Единичные замены |
char[] | Да | Да | Правки по позициям |
StringBuilder | Да | Да | Многократные правки, циклы |
| Интерполяция / конкатенация | Нет | Нет | Сборка новой строки из частей |
⚠️ Внимание: при работе с индексами следите за границами. Обращение к
chars[i]илиsb[i]за пределами длины приведёт к исключениюIndexOutOfRangeExceptionуже во время выполнения — компилятор такую ошибку не поймает.
Если ошибка относится к собственному свойству
Когда в сообщении CS0200 вместо string.this[int] указано имя вашего свойства, причина в том, что свойство объявлено только для чтения:
public class User
{
public string Name { get; } // сеттера нет
}
var user = new User();
user.Name = "Иван"; // CS0200
Варианты исправления зависят от намерения:
- 🔓 Нужно изменять свойство извне — добавьте сеттер:
public string Name { get; set; } - 🏗️ Значение задаётся один раз — присвойте его в конструкторе или используйте
init-аксессор (в современных версиях C#):public string Name { get; init; } - 🔐 Свойство должно быть неизменным — оставьте как есть и пересмотрите код, который пытается его перезаписать.
Почему нельзя просто «обойти» неизменяемость строк
Технически существуют небезопасные приёмы с unsafe-кодом и указателями, позволяющие изменить содержимое строки в памяти. Использовать их в прикладном коде крайне не рекомендуется: из-за интернирования строк такая правка может неожиданно изменить литералы в других частях программы, нарушить работу словарей и привести к трудноотлавливаемым сбоям. Корректный путь — создать новую строку.
CS0200 — не баг, а защита: компилятор напоминает, что строки неизменяемы. Правильное решение — создать новую строку через Replace, char[] или StringBuilder.
Частые вопросы
Можно ли сделать строку в C# изменяемой?
Нет, тип System.String неизменяем по своей архитектуре, и это нельзя отключить. Для изменяемого текстового буфера предназначен класс StringBuilder.
Почему чтение по индексу работает, а запись — нет?
У индексатора string.this[int] реализован только аксессор get. Чтение символа безопасно, а запись нарушила бы неизменяемость строки, поэтому аксессор set отсутствует.
Что быстрее: char[] или StringBuilder?
Для правок по известным индексам массив char[] обычно проще и легковеснее. StringBuilder выигрывает, когда помимо замен есть вставки, удаления и дозапись в конец. Для единичной замены достаточно Replace или Remove + Insert.
Ошибка указывает на моё свойство, а не на строку. Что делать?
Проверьте объявление свойства: если у него есть только get, добавьте set или init, либо присвойте значение в конструкторе. Если свойство задумано как неизменяемое — исправьте код, который пытается его перезаписать.
Сохраняется ли старая строка после Replace?
Да. Метод Replace возвращает новую строку, а исходная остаётся в памяти до тех пор, пока на неё есть ссылки; затем её удалит сборщик мусора. Поэтому результат нужно явно присваивать переменной.