Какую пользу несет неизменяемость типа String

Тип String является неизменяемым типом данных, ведь он не может быть модифицирован после создания. Такая простая особенность открывает для разработчика большое количество преимуществ, характерных не только для строк, а и для других неизменяемых типов данных: структур, делегатов, собственных неизменяемых классов.

Отсутствие побочных эффектов

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

Гарантия потокобезопасности

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

thread_string_1

Два потока могут сколько угодно работать со строкой «abcde» на чтение, в таком случае никакой синхронизации не требуется. Однако необходимость синхронизации двух потоков появилась бы в случае изменяемости типа String, так как они бы оба вносили изменения в совместно используемый объект. Но неизменяемость строк приводит к тому, что любая попытка модификации приводит к созданию нового объекта:

thread_string_2

В нашем случае поток №2 решил добавить к исходной строке символы «efg» и в результате получил в свое распоряжение отдельный объект, не повлияв на работу потока №1.

Безопасное использование в хеш-таблицах

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

Пример кода, который демонстрирует проблему:

public class Number
{
    public int Value { get; set; }
    public override bool Equals(object obj) => obj is Number p && Value == p.Value;
    public override int GetHashCode() => Value.GetHashCode();
}
static void Main()
{
    var values = new Dictionary<Number, string>();
    var key = new Number() { Value = 1 };
    values[key] = "testvalue";
    key.Value = 2;
    var val = values[key]; //KeyNotFoundException в этом месте
}

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

Возможность интернирования строк

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

string a = "ABC";
string b = "ABC";
string c = "ABC";

При запуске кода выше, в куче будет создан единственный объект типа String c содержимым «ABC», на который будут ссылаться переменные «a», «b» и «с». При попытке изменить строку «c» будет создан новый объект String, ссылка на который пойдет только в переменную «c».

interning

Интернирование строк является скрытой оптимизацией, которая приводила бы к побочным эффектам в приложении в случае изменяемости типа String, так как при изменении «c» не создавался бы новый объект, изменялся бы единственный объект, что повлияло бы «a» и «b». Следовательно, интернирование не было бы реализовано вовсе.

Быстрое копирование строк

Копирование строки любого размера выполняется очень быстро за константное время, так сводится к простому возврату ссылки на копируемый объект. Для копирования строки нет необходимости выделять память для нового объекта, выполнять копирование старой строки в новую. Вызов метода Clone на строке просто возвращает указатель this вызывающему коду.

//код класса String
public object Clone()
{
    return (object)this;
}

Заключение

Использование неизменяемого типа String поможет избежать большого количества неприятных побочных эффектов, но с другой стороны можно получить проблемы с производительностью, так как очень частые «изменения» строк приведут к большому количеству аллокаций и нагрузку на сборщик мусора. Подобную проблему в .NET решают при помощи изменяемого StringBuilder’а.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google photo

Для комментария используется ваша учётная запись Google. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s