Ошибка компилятора 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 звучит одинаково для индексатора строки и для свойств без сеттера. Всегда смотрите, какое именно имя указано в тексте ошибки — от этого зависит способ исправления.

📊 В какой ситуации вы столкнулись с ошибкой CS0200?
Пытался изменить символ строки по индексу
Присваивал значение свойству без set
Работал со строкой в цикле
Ошибка в чужом/унаследованном коде

Способ 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

Выполнено: 0 / 5

Для разового изменения 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 возвращает новую строку, а исходная остаётся в памяти до тех пор, пока на неё есть ссылки; затем её удалит сборщик мусора. Поэтому результат нужно явно присваивать переменной.