День четыреста третий. #Оффтоп
Использование Неизменяемых Структур Данных в C#
“Если у вас есть аксессор (get) свойства, у него необязательно должен быть мутатор (set).”
В нескольких постах на моём канале уже упоминалось, что структуры должны быть неизменяемы или что в лучших практиках советуют делать объекты неизменяемыми, когда это уместно. Но что это значит на практике и что это даёт?
Известная цитата знаменитого разработчика компьютерных игр Джона Кармака гласит: «Большая часть недостатков в разработке программного обеспечения связана с тем, что программисты не до конца понимают все возможные состояния, в которых может выполняться их код.»
То есть проблемы возникают из-за того, что значения переменных постоянно меняются и очень сложно отследить все возможные изменения и предсказать, как программа поведёт себя при том или ином значении переменной.
Если мы имеем изменяющиеся данные, к которым могут обращаться несколько потребителей (потоков), сразу встаёт вопрос синхронизации. Неизменяемость данных облегчает параллельную обработку. Если объект не может изменяться, нам не нужно думать, что будет в случае его изменения. Это позволяет сосредоточиться на задаче и не отвлекаться на варианты использования, которые непосредственно к ней не относятся.
Когда мы видим в типе мутатор (set) свойства, сразу возникают вопросы:
- Он здесь потому, что я могу менять значение?
- Он здесь потому, что от меня требуется изменить значение?
- А что будет, если я изменю значение?
Кроме того, передача изменяемых данных в метод даёт этому методу «разрешение» изменять данные, если очень захочется. Иногда это действительно нужно, но далеко не всегда.
Поддержка неизменяемых данных в языке C#
Неизменяемые типы были в C# с самого начала. Это строки или структура DateTime. В C#7.2 появились структуры только для чтения, что позволило значительно увеличить производительность. Анонимные типы тоже не только неизменяемы, но и сравниваются по значению, а не по ссылке. Начиная с 6 версии языка можно объявлять автосвойства только для чтения, указывая только аксессор get
:
public class Person {В C# есть вид неизменяемых коллекций
public string FirstName { get; }
public string LastName { get; }
…
}
System.Collections.Immutable
, которые работают по тому же принципу, что и строки (добавление/удаление элемента возвращает новую неизменяемую коллекцию):var list1 = ImmutableList.Create<string>("one");
var list2 = list1.Add("two");
list1
и list2
– это разные объекты, в list1
остался 1 элемент.Кроме того, можно использовать метод
List<T>.AsReadOnly()
, но он не такой мощный, т.к. получившуюся коллекцию вообще нельзя изменять:var list3 = new List<string> { "three" }.AsReadOnly();Советы по использованию
list3.Add("four"); // ошибка компиляции
1. Используйте неизменяемые объекты как один из методов защитного программирования. Также, как вы делаете проверку на null, задумывайтесь при создании типа, должны ли его свойства меняться.
2. Делайте объекты изменяемыми, только если это действительно необходимо. Например, если неизменяемые объекты сильно усложняют код (как при работе с некоторыми библиотеками или фреймворками, вроде Entity Framework).
3. Все члены команды должны договориться об использовании этого правила, т.к. различные взгляды на этот вопрос приведут только к путанице и конфликтам.
4. Если вы навязываете неизменяемость, жертвуя читаемостью и лёгкостью поддержки кода, вы не улучшаете качество проекта, а заменяете одну проблему другой.
Источник: Спенсер Шнайденбах - https://www.youtube.com/watch?v=aUbXGs7YTGo (видео на английском)
>>Click here to continue<<