Наилучшая практика использования 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 в Dynamo SDK (как и с другими).

Мне кажется странным писать, public string Id { get; set; } = "";потому что это никогда не произойдет, так Idкак это PK и никогда не может быть нулевым. Какая польза будет "", даже если это все равно как-нибудь?

Так что лучше в этом деле?

  • Должен ли я отметить их всех так, string?чтобы они говорили, что они могут быть нулевыми, хотя некоторые никогда не должны быть.
  • Должен ли я инициализировать Idи Nameс, ""потому что они никогда не должны быть нулевыми, и это показывает намерение, хотя ""никогда не будет использоваться.
  • Некоторая комбинация выше

Пожалуйста, обратите внимание: это о C # 8 обнуляемых ссылочных типов. Если вы не знаете, на что лучше всего не отвечать.

BritishDeveloper
источник
Это немного грязно, но вы можете просто ударить #pragma warning disable CS8618в верхней части файла.
20
7
Вместо = ""этого вы можете использовать = null!для инициализации свойство, которое, как вы знаете, никогда не будет эффективно null(когда компилятор не может этого знать). Если это Descriptionможет быть юридически null, это должно быть объявлено string?. В качестве альтернативы, если проверка обнуляемости для DTO более неприятна, чем справка, вы можете просто обернуть тип в #nullable disable/, #nullable restoreчтобы отключить NRT только для этого типа.
Йерун Мостерт
@JeroenMostert Вы должны поставить это как ответ.
Магнус
3
@Magnus: я не хочу отвечать на любой вопрос, спрашивая о «лучших методах»; такие вещи широки и субъективны. Я надеюсь, что ОП сможет использовать мой комментарий для разработки собственной «лучшей практики».
Йерун Мостерт
1
@ IvanGarcíaTopete: Хотя я согласен с тем, что использование строки для первичного ключа является необычным и даже нецелесообразным в зависимости от обстоятельств, выбор типа данных OP довольно не имеет отношения к вопросу. Это так же легко можно применить к обязательному необнуляемому строковому свойству, которое не является первичным ключом, или даже строковому полю, являющемуся частью составного первичного ключа, и этот вопрос все еще остается.
Джереми Кейни

Ответы:

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 атрибуты постусловия для управления обнуляемостью

  • 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является PK и никогда не может быть нулевым».

Чтобы помочь вам сохранить это обязательство, вы можете дополнительно добавить аннотацию к свойству [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в случае статического анализа потоков в C #, и в то же время позволит значению оставаться неинициализированным между созданием объекта и заполнением объекта.

Примечание: Как уже упоминался, вы также можете достичь практически идентичного результата путем присвоения Idзначения по умолчанию либо default!или null!. По общему признанию, это отчасти стилистическое предпочтение. Я предпочитаю использовать аннотации обнуляемости, так как они более явные и обеспечивают детальный контроль, в то время как это легко использовать !как способ затянуть компилятор. Явная инициализация свойства значением также раздражает меня, если я знаю, что никогда не буду использовать это значение, даже если это значение по умолчанию.

Джереми Кейни
источник
-2

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

Дино
источник
8
Он говорит об обнуляемых ссылочных типах в C # 8
Magnus