Meilleure pratique d'utilisation des types de référence Nullable pour les DTO

20

J'ai un DTO qui est rempli en lisant une table DynamoDB. Disons que cela ressemble à ceci actuellement:

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
}

Existe-t-il des meilleures pratiques en cours d'élaboration pour y faire face? Je préfère éviter un constructeur sans paramètre car cela joue mal avec l'ORM dans le SDK Dynamo (ainsi que d'autres).

Cela me semble étrange d'écrire public string Id { get; set; } = "";car cela ne se produira jamais car il Ids'agit d'un PK et ne peut jamais être nul. Quelle serait l'utilité ""même si elle le faisait de toute façon?

Alors, quelle est la meilleure pratique à ce sujet?

  • Dois-je les marquer tous string?pour dire qu'ils peuvent être nuls même si certains ne devraient jamais l'être.
  • Dois-je initialiser Idet Nameavec ""car ils ne doivent jamais être nuls et cela montre l'intention même si ""elle ne sera jamais utilisée.
  • Une combinaison de ce qui précède

Veuillez noter: il s'agit de types de référence annulables C # 8 Si vous ne savez pas ce qu'il vaut mieux, ne répondez pas.

BritishDeveloper
la source
C'est un peu sale, mais vous pouvez simplement gifler #pragma warning disable CS8618en haut du fichier.
Vingt
7
Au lieu de cela = "", vous pouvez utiliser = null!pour initialiser une propriété que vous savez ne sera jamais efficace null(lorsque le compilateur n'a aucun moyen de le savoir). Si cela Descriptionpeut être légalement possible null, il doit être déclaré a string?. Alternativement, si la vérification de la nullité pour le DTO est plus gênante que l'aide, vous pouvez simplement envelopper le type dans #nullable disable/ #nullable restorepour désactiver les NRT pour ce type uniquement.
Jeroen Mostert
@JeroenMostert Vous devriez mettre cela comme réponse.
Magnus
3
@Magnus: Je suis réticent à répondre à toute question demandant des "meilleures pratiques"; ces choses sont larges et subjectives. J'espère que le PO pourra utiliser mon commentaire pour développer sa propre "meilleure pratique".
Jeroen Mostert
1
@ IvanGarcíaTopete: Bien que je convienne que l'utilisation d'une chaîne pour une clé primaire est inhabituelle, et peut même être déconseillée selon les circonstances, le choix du type de données par le PO n'est pas pertinent pour la question. Cela pourrait tout aussi bien s'appliquer à une propriété de chaîne non nullable requise qui n'est pas la clé primaire, ou même un champ de chaîne qui fait partie d'une clé primaire composite, et la question resterait.
Jeremy Caney

Réponses:

12

En option, vous pouvez utiliser le defaultlittéral en combinaison avec lenull forgiving operator

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

Étant donné que votre DTO est rempli à partir de DynamoDB, vous pouvez utiliser des MaybeNull/NotNull attributs de postcondition pour contrôler la nullité

  • MaybeNull Une valeur de retour non nullable peut être nulle.
  • NotNull Une valeur de retour nullable ne sera jamais nulle.

Mais ces attributs n'affectent que l'analyse annulable pour les appelants des membres qui sont annotés avec eux. En règle générale, vous appliquez ces attributs aux retours de méthode, aux propriétés et aux indexeurs.

Ainsi, vous pouvez considérer toutes vos propriétés comme non nulles et les décorer avec un MaybeNullattribut, en indiquant qu'elles renvoient une nullvaleur possible

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

L'exemple suivant montre l'utilisation de la Itemclasse mise à jour . Comme vous pouvez le voir, la deuxième ligne n'affiche pas d'avertissement, mais la troisième

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.

Ou vous pouvez rendre toutes les propriétés nullables et utiliser NoNullpour indiquer que la valeur de retour ne peut pas être null( Idpar exemple)

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

L'avertissement sera le même que dans l'exemple précédent.

Il existe également des AllowNull/DisallowNull attributs de condition préalable pour les paramètres d'entrée, les propriétés et les indicateurs d'indexation, fonctionnant de la même manière.

  • AllowNull Un argument d'entrée non nullable peut être nul.
  • DisallowNull Un argument d'entrée nullable ne doit jamais être null.

Je ne pense pas que cela vous aidera, car votre classe est remplie à partir de la base de données, mais vous pouvez les utiliser pour contrôler la nullité des setters de propriétés, comme ceci pour la première option

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

Et pour le deuxième

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

Quelques détails utiles et exemples de post / conditions préalables peuvent être trouvés dans cet article de devblog

Pavel Anikhouski
la source
6

La réponse du manuel dans ce scénario est d'utiliser un string?pour votre Idpropriété, mais aussi de le décorer avec l' [NotNull]attribut:

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

Référence: Selon la documentation , l' [NotNull]attribut "spécifie qu'une sortie n'est pas nulle même si le type correspondant le permet."

Alors, que se passe-t-il exactement ici?

  1. Tout d' abord, le string?type de retour empêche le compilateur de vous avertir que la propriété est non initialisée lors de la construction et donc par défaut à null.
  2. Ensuite, l' [NotNull]attribut empêche un avertissement lors de l'attribution de la propriété à une variable non nullable ou de la tentative de déréférencement, car vous informez l'analyse de flux statique du compilateur que, dans la pratique , cette propriété ne le sera jamais null.

Avertissement: comme dans tous les cas impliquant le contexte de nullité de C #, rien ne vous empêche techniquement de toujours renvoyer une nullvaleur ici et donc, potentiellement, d'introduire des exceptions en aval; c'est-à-dire qu'il n'y a pas de validation d'exécution prête à l'emploi. Tout ce que C # fournit est un avertissement du compilateur. Lorsque vous introduisez, [NotNull]vous remplacez efficacement cet avertissement en lui donnant un indice sur votre logique métier. En tant que tel, lorsque vous annotez une propriété avec [NotNull], vous prenez la responsabilité de votre engagement que "cela ne se produira jamais car il Ids'agit d'un PK et ne peut jamais être nul".

Afin de vous aider à maintenir cet engagement, vous pouvez en outre souhaiter annoter la propriété avec l' [DisallowNull]attribut:

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

Référence: Selon la documentation , l' [DisallowNull]attribut "spécifie qu'il nullest interdit en tant qu'entrée même si le type correspondant le permet."

Cela peut ne pas être pertinent dans votre cas, car les valeurs sont attribuées via la base de données, mais l' [DisallowNull]attribut vous avertira si vous essayez d'attribuer une nullvaleur (capable) à Id, même si le type de retour permet autrement de le faire. null . À cet égard, Idagirait exactement comme un stringen ce qui concerne l' analyse de flux statique de C #, tout en permettant également à la valeur de rester non initialisée entre la construction de l'objet et la population de la propriété.

Remarque: Comme d'autres l'ont mentionné, vous pouvez également obtenir un résultat pratiquement identique en affectant la Idvaleur par défaut de default!ou null!. Il s'agit certes d'une préférence stylistique. Je préfère utiliser les annotations de nullité car elles sont plus explicites et fournissent un contrôle granulaire, alors qu'il est facile d'en abuser !comme moyen de fermer le compilateur. L'initialisation explicite d'une propriété avec une valeur me harcèle également si je sais que je ne vais jamais utiliser cette valeur, même si c'est la valeur par défaut.

Jeremy Caney
la source
-2

La chaîne est un type de référence et toujours nullable, vous n'avez rien à faire de spécial. Vous pourriez avoir des problèmes uniquement plus tard si vous souhaitez mapper ce type d'objet à un autre, mais vous pouvez gérer cela plus tard.

dino
la source
8
Il parle de types de référence Nullable en C # 8
Magnus