Наткнулся на вот эту статью, которая посвящена фишкам, которые с высокой вероятностью будут добавлены в новую версию языка.
Наткнулся там на Records.
Немного не понятно, зачем нужен кастрированный класс.
С каждой новой версией C# в него добавляют все больше фишек из функционального программирования, и Records - одна из таких фишек. Из парадигмы функционального программирования:
Основной особенностью функционального программирования, определяющей как преимущества, так и недостатки данной парадигмы, является то, что в ней реализуется модель вычислений без состояний. Если императивная программа на любом этапе исполнения имеет состояние, то есть совокупность значений всех переменных, и производит побочные эффекты, то чисто функциональная программа ни целиком, ни частями состояния не имеет и побочных эффектов не производит. То, что в императивных языках делается путём присваивания значений переменным, в функциональных достигается путём передачи выражений в параметры функций. Непосредственным следствием становится то, что чисто функциональная программа не может изменять уже имеющиеся у неё данные, а может лишь порождать новые путём копирования и/или расширения старых.
Records является просто иммутабельным классом, состояние которого невозможно изменить. В некоторых случаях такой подход уменьшает количество возможных ошибок. Возьмем простой код:
MyClass something = new MyClass(arg1, arg2, arg3);
MyMethod(something);
Что произошло в MyMethod
? Никто не знает. Изменилось ли состояние something
? Находиться ли он валидном состоянии или нужно после вызова метода сразу проверить что-то и вбросить Exception
?
Самый известный и частоиспользуемый иммутабельный класс в C# это String
. Я думаю все согласятся, что допустить ошибку при работе со строками очень сложно. Этот класс только выиграл от иммутабельности. Конечно, это не совсем хорошая аналогия с Records, потому что в строк есть свои методы, но если очень нужно мы можем добавлять методы расширения для Records. Несмотря на то, что это не совсем удобно, все равно кода в итоге получиться меньше, чем при имлементации иммутабельного класса вручную.
Это синтаксический сахар для более короткого объявления класса, который просто содержит набор полей.
В примере из статьи показано, что следующий класс:
public class Sword : IEquatable<Sword>
{
public int Damage { get; }
public int Durability { get; }
public Sword(int Damage, int Durability)
{
this.Damage = Damage;
this.Durability = Durability;
}
public bool Equals(Sword other)
{
return Equals(Damage, other.Damage) && Equals(Durability, other.Durability);
}
public override bool Equals(object other)
{
return (other as Sword)?.Equals(this) == true;
}
public override int GetHashCode()
{
return (Damage.GetHashCode() * 17 + Durability.GetHashCode());
}
public void Deconstruct(out int Damage, out int Durability)
{
Damage = this.Damage;
Durability = this.Durability;
}
public Sword With(int Damage = this.Damage, int Durability = this.Durability) =>
new Sword(Damage, Durability);
}
Можно будет объявить так:
public class Sword(int Damage, int Durability);
И компилятор сам додумает все остальное: конструкторы, свойства, сравнение.
Если нужен класс с функционалом не по-умолчанию, то всегда можно воспользоваться полным синтаксисом объявления класса. Это нововведение позволит сэкономить код в тех случаях, когда нужен именно класс с набором полей и ничего больше.
Экономия кода указывается как основное обоснование в предложении на Github:
Motivation
A significant number of type declarations in C# are little more than aggregate collections of typed data. Unfortunately, declaring such types requires a great deal of boilerplate code. Records provide a mechanism for declaring a datatype by describing the members of the aggregate along with additional code or deviations from the usual boilerplate, if any.
Обоснование
Значительная часть объявленных типов в C# немногим отличается от набора типизированных данных. К сожалению, объявление таких типов требует большого количества шаблонного кода. Записи предоставляют механизм для объявления типа данных путем перечисления членов набора либо отличий от стандартного шаблона, если таковые имеются.
record class - это не "кастрированный класс", а иммутабельный класс, у которого часть важных методов уже реализована компилятором. Слово "иммутабельный" тут - не недостаток, а важная фича.
Такие классы просто необходимы для следующих задач:
использование в качестве ключа в словаре: каждый раз, когда вам нужен словарь по двум параметрам, приходится или использовать кортежи, или использовать анонимные типы, или писать свой класс с методами Equals и GetHashCode, причем использовать кортежи в публичном API - дурной тон, а анонимные типы так и вовсе приводят к нетипизированному ключу;
кеширование данных: данные в кеше обязаны быть неизменяемыми, чего можно достигнуть или защитным копированием - или иммутабельностью;
многопоточный доступ: к иммутабельным данным можно обращаться из любых потоков без блокировок.
Ниже представлен некоторый псведокод, в котором опущены многие детали, включая спецификаторы доступа и прочую шелуху, которая не важна для понимания. Так же используется минимальное количество полей, чтобы сократить код.
Представьте, что у Вас есть такой класс:
class Person
{
string name;
}
И вот такой класс для хранения экземпляров предыдущего:
class CompanyRegister
{
List<Person> persons;
}
CompanyRegister
— главное хранилище всех персон в программе, поэтому, если кому-то нужно добавить персону, или получить по ней данные, он обращается к этому классу. Для получения персоны по какому-то признаку нам понадобится функция GetPerson()
, но что она будет возвращать? Пусть она возвращает Person
, является ли это проблемой? Безусловно. Если мы возвращаем Person
то любая сущность может изменить этот объект вне CompanyRegister
!
Но это противоречит основам нашего класса: только он отвечает за изменения (он может оповещать другие сущности, либо же заниматься каким-то слежением за изменениями — не важно). Как нам решить проблему? Если бы мы писали на C++, то мы бы сделали возвращаемый тип const Person&
, и проблема была бы решена. Но у нас C#, который при всех своих достоинствах, не может похвастаться наличием таких средства как C++ в части «оконстанивания» и предотвращения лишних копий.
Кстати о копиях, мы могли быть сделать Person
структурой, но это повлекло бы излишнее копирование, которого по возможности все стараются избегать. Исходя из того, что язык в C++ превращать никто не собирается, а в C# [почти] всё есть ссылка, то разработчики обратили свой взор в сторону функционального программирования и взяли на вооружение неизменяемые типы. Реализация таких типов в C# это куча кода, который нужно писать для каждого класса, либо же использовать всяческие кодогенераторы. Вот как, к примеру, мы могли бы сделать наш Person
неизменяемым:
class Person
{
string name;
Person(string name)
{
name = name;
}
Builder GetBuilder()
{
return new Builder(this);
}
class Builder
{
string name;
Builder(Person person)
{
name = person.name;
}
Person ToImmutable()
{
return new Person(name: name);
}
}
}
Выглядит чудесно, не правда ли? А теперь представьте, что у нас не одно поле, а с десяток полей. Как Вам? Так вот Records
делает то же самое, только писать всё это не нужно, а синтаксис создания новых объектов на основании старых становится куда удобнее.
Как часто всё это нужно? Очень. Ведь у нас очень часто есть данные, за которые кто-то отвечает, и этот кто-то не хочет, чтобы их меняли все кому не лень за пределами этого класса. Это очень часто встречается в GUI-приложениях, когда из модели данные передаются в представление (VM и прочие).
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
Всем приветКак мне перевести слово с русского на украинский в PHP? Я получаю стрингу со словом и мне нужно его перевести, подскажите, пожалуйста,...
Подскажите как правильно установить расширение php пошагово ? к примеру на сервере не хватает расширения ZipArchive мануал http://phpnet/manual/ru/book