C#: [required] keyword, что это?
Итак, после двухлетнего ожидания и многих обещаний мы можем пощупать LTS версию свежего .net 8, более менее спокойно мигрировать наши кодовые базы и потестировать от всей души свежую версию языка!
Какой-то жуткий мутант, не находите?
Формально, C# 11 доступен с .net 7, но, на деле, он там работал очень плохо. Я один раз попробовал потестить около года назад и получил море ошибок, так что решил подождать
История появления required keyword в языке
Возьмём какой-нибудь класс
public class Money
{
public decimal Amount { get; set; }
public string Currency { get; set; }
}
Теперь мы можем его создавать и использовать как захотим
var money = new Money
{
Amount = 15m,
Currency = "RUB",
}
Проблема в том, что создав класс Money с 15 rub мы передаем его в другие методы. И в какой-то момент кому-то может прийти в голову не создавать новый класс (например, в целях оптимизации), а просто задать новый Amount. Такого конечно же допускать нельзя! Для таких людей и целей у нас появилось слово init
public class Money
{
public decimal Amount { get; init; }
public string Currency { get; init; }
}
Теперь будет невозможно менять наш класс после его создания. Но вот проблема, можно забыть задать валюту!!!
var money = new Money
{
Amount = 15m,
}
В чем теперь наши деньги? Опытные разработчики скажут, что данная проблема решается конструктором
public class Money
{
public Money(string currency)
{
Currency = currency;
}
public decimal Amount { get; init; }
public string Currency { get; }
}
Но тогда у нас появляется этот самый конструктор и запись класса превращается в монстра Франкенштейна! Для решения данной проблемы ребята придумали record
public record Money(string Currency, decimal Amount);
И, в принципе, здесь можно было бы остановиться, потому что у рекордов есть ряд приятных преимуществ. Но вот что делать, есть нам нужны DTO? Для DTO нужно будет пометить всё атрибутами
public class MoneyDto
{
[Required]
[JsonPropertyName("amount")]
public decimal Amount { get; init; }
[Required]
[StringLength(maximumLength: 3, MinimumLength = 3)]
[JsonPropertyName("currency")]
public string Currency { get; init; }
}
Рекорд тоже можно обложить атрибутами, но там начинается просто ужас нечитаемый
И вот здесь уже появляется наш герой: ключевое слово required
Как минимум, очень хочешь им быть
Required keyword в действии
С данным словом наш класс становится намного элегантнее!
public class MoneyDto
{
[JsonPropertyName("amount")]
public required decimal Amount { get; init; }
[StringLength(maximumLength: 3, MinimumLength = 3)]
[JsonPropertyName("currency")]
public required string Currency { get; init; }
}
Теперь класс не только можно создать только с данными свойствами, но еще и запретить их менять в процессе. Ииии, тестируем!
Без required keyword
{
"Amount": [
"The Amount field is required."
],
"Currency": [
"The Currency field is required."
]
}
C required keyword
{
"$": [
"JSON deserialization for type 'NewNet.MoneyDto' was missing required properties, including the following: amount, currency"
],
"body": [
"The body field is required."
]
}
Для начала, мне очень нравится, что всё работает (Я просто люблю, когда всё работает). Но, как мы видим — первая наша проблема, это изменился контракт ошибки с красивого, на непонятно что с »$». Вторая — если атрибут nullable, то его всё еще можно передавать через null. Например, вот так
public required decimal? Amount { get; init; }
{
"amount": null,
"currency": "RUB"
}
И всё отработает. Это надо держать в уме. Вообще, это очень логично, мы же сами разрешили, null и пометили свойство required, но всё равно чуть-чуть остается ощущение будто обманули -_-
Заключение
Как всегда приятно видеть, что язык развивается и становится лучше, продуманнее, глубже. Но детали всё еще не проработаны, всё еще нужно учитывать достаточно много и писать кое какой boilerplate code или как то что то по определенному настраиваем. В нашем случае очевидно, что проблема в валидаторе деерилизатора json и наша dto просто не доходит до валидации по атрибутам. Я очень хочу, чтобы по прочтению данной статьи вы нашли где у вас в проекте может понадобиться столь чудный атрибут, а пока что, на этом всё