Найкраща практика використання Nullable Reference Types для DTO


20

У мене є DTO, який заповнюється читанням з таблиці DynamoDB. Скажіть, це виглядає приблизно так:

public class Item
{
    public string Id { get; set; } // PK so technically cannot be null
    public string Name { get; set; } // validation to prevent nulls but this doesn't stop database hacks
    public string Description { get; set; } // can be null
}

Чи існує якась найкраща практика для боротьби з цим? Я вважаю за краще уникати безпараметричних конструкторів, оскільки це погано грає з ORM в SDK "Динамо" (як і інші).

Мені здається дивним писати, public string Id { get; set; } = "";тому що цього ніколи не станеться, оскільки Idце ПК і ніколи не може бути нульовим. Яка користь була ""б, навіть якби це все-таки все-таки було?

То будь-яка найкраща практика щодо цього?

  • Чи повинен я відзначити їх усі, string?щоб сказати, що вони можуть бути недійсними, хоча деякі ніколи не повинні бути.
  • Повинен чи я ініціалізацію Idі Nameз , ""тому що вони не повинні ніколи бути порожнім , і це показує намір , навіть якщо ""ніколи не буде використовуватися.
  • Деякі поєднання вище

Зверніть увагу: мова йде про нульові посилання C # 8, якщо ви не знаєте, що на них краще не відповідати.


Це трохи брудно, але ви можете просто ляпнути #pragma warning disable CS8618вгорі файлу.
Двадцять

7
Замість цього = "", ви можете використовувати = null!ініціалізацію властивості, яке, як відомо, ніколи не буде ефективно null(коли компілятор не може цього знати). Якщо це Descriptionможе бути юридично null, його слід оголосити а string?. Крім того, якщо перевірка нульовості для DTO викликає більше клопоту, ніж допомога, ви можете просто загорнути тип #nullable disable/ #nullable restoreвимкнути NRT тільки для цього типу.
Jeroen

@JeroenMostert Ви повинні поставити це як відповідь.
Магнус

3
@Magnus: я не бажаю відповідати на будь-яке запитання із проханням про "кращі практики"; такі речі широкі та суб'єктивні. Я сподіваюся, що ОП може використовувати мій коментар для розробки власної "найкращої практики".
Jeroen

1
@ IvanGarcíaTopete: Хоча я погоджуюся, що використання рядка для первинного ключа є незвичним і навіть може бути недоцільним залежно від обставин, вибір типу даних ОП в цілому не має значення. Це може так само легко застосуватись до необхідного, нерегульованого рядкового властивості, яке не є первинним ключем, або навіть рядкового поля, що є частиною складеного первинного ключа, і питання все одно буде стояти.
Джеремі Кейні

Відповіді:


12

Як варіант, ви можете використовувати defaultлітерал у поєднанні зnull forgiving operator

public class Item
{
    public string Id { get; set; } = default!;
    public string Name { get; set; } = default!;
    public string Description { get; set; } = default!;
}

Оскільки ваш DTO заповнюється з DynamoDB, ви можете використовувати MaybeNull/NotNull атрибути postcondition для контролю зведеності

  • MaybeNull Невідмінне значення повернення може бути нульовим.
  • NotNull Нульове повернене значення ніколи не буде нульовим.

Але ці атрибути впливають лише на зворотний аналіз для абонентів членів, які з ними помічені. Зазвичай ви застосовуєте ці атрибути до повернень методів, властивостей та індексаторів.

Отже, ви можете вважати всі свої властивості ненульовими та прикрашати їх MaybeNullатрибутом, вказуючи на те, що вони повертають можливе nullзначення

public class Item
{
    public string Id { get; set; } = "";
    [MaybeNull] public string Name { get; set; } = default!;
    [MaybeNull] public string Description { get; set; } = default!;
}

Наступний приклад показує використання оновленого Itemкласу. Як бачите, другий рядок не містить попередження, а третій

var item = new Item();
string id = item.Id;
string name = item.Name; //warning CS8600: Converting null literal or possible null value to non-nullable type.

Або ви можете зробити всі властивості нульовими та використовувати, NoNullщоб вказати, що повернене значення не може бути null( Idнаприклад)

public class Item
{
    [NotNull] public string? Id { get; set; }
    public string? Name { get; set; }
    public string? Description { get; set; }
}

Попередження буде аналогічним попередньому прикладу.

Також є AllowNull/DisallowNull атрибути передумови для вхідних параметрів, властивостей та індексаторів, що працюють аналогічно.

  • AllowNull Ненульовий вхідний аргумент може бути нульовим.
  • DisallowNull Аргумент, що зводиться до нуля, ніколи не повинен бути нульовим.

Я не думаю, що це допоможе вам, оскільки ваш клас заповнений з бази даних, але ви можете використовувати їх для управління нульовістю параметрів властивостей, як це для першого варіанту

[MaybeNull, AllowNull] public string Description { get; set; }

І на другий

[NotNull, DisallowNull] public string? Id { get; set; }

Деякі корисні деталі та приклади публікації / передумов можна знайти у цій статті devblog


6

Відповідь підручника в цьому сценарії полягає у використанні значка string?для вашої Idвласності, а також прикрасити його [NotNull]атрибутом:

public class Item
{
  [NotNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}

Довідка: Відповідно до документації , [NotNull]атрибут "вказує, що результат не є нульовим, навіть якщо відповідний тип дозволяє це".

Отже, що саме тут відбувається?

  1. По- перше, string?тип повертається значення, компілятор НЕ попереджав про те , що властивість инициализирован під час будівництва і , таким чином , буде по замовчуванням в null.
  2. Тоді [NotNull]атрибут запобігає попередженню, коли присвоює властивість ненульовій змінній або намагається її знеструмити, оскільки ви повідомляєте статичний аналіз потоку компілятора, що на практиці цього властивості ніколи не буде null.

Попередження: Як і у всіх випадках, що стосуються контексту зведення на C #, ніщо технічно не заважає вам все-таки повертати nullзначення тут і, таким чином, потенційно вводити деякі винятки нижче за течією; тобто немає перевірки виконуваної роботи поза вікном. Все, що надається C #, - це попередження компілятора. Коли ви знайомитесь, [NotNull]ви фактично відміняєте це попередження, даючи йому підказку про вашу логіку бізнесу. Таким чином, коли ви коментуєте властивість [NotNull], ви берете на себе відповідальність за своє зобов'язання, що "цього ніколи не станеться, оскільки Idце ПК і ніколи не може бути недійсним".

Щоб допомогти вам зберегти це зобов’язання, ви можете додатково побажати анотувати ресурс за допомогою [DisallowNull]атрибута:

public class Item
{
  [NotNull, DisallowNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}

Довідка: Згідно з документацією , [DisallowNull]атрибут "вказує, що nullвін заборонений як вхід, навіть якщо відповідний тип дозволяє це".

Це може бути нерелевантним у вашому випадку, оскільки значення призначаються через базу даних, але [DisallowNull]атрибут подасть вам попередження, якщо ви коли-небудь намагатиметеся призначити null(в змозі) значення Id, навіть якщо тип повернення інакше дозволяє це зробити нульовий . У зв'язку з цим , Idбуде діяти точно подібно string, наскільки аналіз статичного потоку С # стурбований, а також дозволяє значенням залишатися Ініціалізувати між будівництвом об'єкта та населенням власності.

Примітка. Як зазначали інші, ви також можете досягти практично однакового результату, призначивши значення Idза замовчуванням default!або null!. Це, правда, дещо стилістичне уподобання. Я вважаю за краще використовувати анотації про мінливість, оскільки вони більш чіткі і забезпечують детальний контроль, тоді як легко зловживати !як спосіб закриття компілятора. Явна ініціалізація властивості зі значенням також натякає на мене, якщо я знаю, що я ніколи не буду використовувати це значення - навіть якщо це за замовчуванням.


-2

Рядок - це еталонний тип і завжди мінливий, вам не потрібно робити нічого особливого. У вас можуть виникнути проблеми лише пізніше, якщо ви хочете зіставити цей тип об'єкта з іншим, але ви можете вирішити це пізніше.


8
Він говорить про нульові еталонні типи в C # 8
Магнус
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.