Best practice per l'utilizzo dei tipi di riferimento Nullable per i DTO


20

Ho un DTO che viene popolato leggendo da una tabella DynamoDB. Di 'che al momento sembra così:

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
}

Esistono delle migliori pratiche per lo sviluppo di questo? Preferirei evitare un costruttore senza parametri poiché questo gioca male con l'ORM nell'SDK di Dynamo (così come altri).

Mi sembra strano scrivere public string Id { get; set; } = "";perché questo non accadrà mai poiché Idè un PK e non può mai essere nullo. A che serve ""anche se lo facesse comunque?

Quindi qualche buona pratica su questo?

  • Devo contrassegnarli tutti string?per dire che possono essere nulli anche se alcuni non dovrebbero mai esserlo.
  • Devo inizializzare Ide Namecon ""perché non dovrebbero mai essere nulli e questo dimostra l'intento anche se ""non sarebbe mai stato usato.
  • Qualche combinazione di cui sopra

Nota: si tratta di tipi di riferimento annullabili C # 8 Se non sai cosa sono meglio non rispondere.


È un po 'sporco, ma puoi semplicemente dare uno schiaffo #pragma warning disable CS8618nella parte superiore del file.
20

7
Piuttosto che = "", puoi usare = null!per inizializzare una proprietà che sai che non sarà mai efficace null(quando il compilatore non ha modo di saperlo). Se Descriptionpuò essere legalmente null, dovrebbe essere dichiarato a string?. In alternativa, se il controllo della nullità per il DTO è più fastidioso dell'aiuto, è possibile semplicemente avvolgere il tipo in #nullable disable/ #nullable restoreper disattivare i NRT solo per questo tipo.
Jeroen Mostert,

@JeroenMostert Dovresti metterlo come una risposta.
Magnus,

3
@Magnus: sono riluttante a rispondere a qualsiasi domanda che richieda "migliori pratiche"; tali cose sono ampie e soggettive. Spero che l'OP possa utilizzare il mio commento per sviluppare le proprie "migliori pratiche".
Jeroen Mostert,

1
@ IvanGarcíaTopete: Anche se concordo sul fatto che l'uso di una stringa per una chiave primaria è insolito e può anche essere sconsigliato a seconda delle circostanze, la scelta del tipo di dati del PO è piuttosto irrilevante per la domanda. Questo potrebbe applicarsi altrettanto facilmente a una proprietà stringa richiesta, non nullable che non è la chiave primaria, o anche a un campo stringa che fa parte di una chiave primaria composita, e la domanda sarebbe ancora valida.
Jeremy Caney,

Risposte:


12

Come opzione, puoi usare il valore defaultletterale in combinazione connull forgiving operator

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

Poiché il DTO è popolato da DynamoDB, è possibile utilizzare gli MaybeNull/NotNull attributi postcondizione per controllare la nullità

  • MaybeNull Un valore restituito non annullabile può essere nullo.
  • NotNull Un valore di ritorno nullable non sarà mai nullo.

Ma questi attributi influiscono solo sull'analisi nullable per i chiamanti dei membri che sono annotati con loro. In genere, si applicano questi attributi a restituitori di metodi, proprietà e getter di indicizzatori.

Quindi, puoi considerare tutte le tue proprietà non annullabili e decorarle con MaybeNullattributi, indicando che restituiscono un nullvalore possibile

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

L'esempio seguente mostra l'utilizzo della Itemclasse aggiornata . Come puoi vedere, la seconda riga non mostra avvisi, ma la terza lo fa

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.

Oppure puoi rendere tutte le proprietà nulle e utilizzare NoNullper indicare che il valore restituito non può essere null( Idad esempio)

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

L'avviso sarà lo stesso dell'esempio precedente.

Esistono anche AllowNull/DisallowNull attributi preliminari per parametri di input, proprietà e setter degli indicizzatori, che funzionano allo stesso modo.

  • AllowNull Un argomento di input non annullabile può essere nullo.
  • DisallowNull Un argomento di input nullable non dovrebbe mai essere nullo.

Non penso che ti aiuterà, poiché la tua classe è popolata dal database, ma puoi usarli per controllare la nullità dei setter delle proprietà, come questo per la prima opzione

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

E per il secondo

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

Alcuni utili dettagli ed esempi di post / precondizioni sono disponibili in questo articolo di devblog


6

La risposta del libro di testo in questo scenario è utilizzare a string?per la tua Idproprietà, ma anche decorarla con l' [NotNull]attributo:

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

Riferimento: secondo la documentazione , l' [NotNull]attributo "specifica che un output non è nullo anche se il tipo corrispondente lo consente".

Quindi, cosa sta succedendo esattamente qui?

  1. Innanzitutto, il string?tipo restituito impedisce al compilatore di avvisare che la proprietà non è inizializzata durante la costruzione e quindi verrà automaticamente impostata su null.
  2. Quindi, l' [NotNull]attributo impedisce un avviso quando si assegna la proprietà a una variabile non nullable o si tenta di dereferenziarla poiché si sta informando l'analisi del flusso statico del compilatore che, in pratica , questa proprietà non sarà mai null.

Avvertenza: come in tutti i casi che riguardano il contesto di nullità di C #, non c'è nulla di tecnicamente che ti impedisce di restituire un nullvalore qui e quindi, potenzialmente, di introdurre alcune eccezioni a valle; cioè, non esiste una convalida di runtime immediata. Tutto ciò che C # fornisce è un avvertimento del compilatore. Quando ti [NotNull]presenti, stai effettivamente ignorando quell'avvertimento dandogli un suggerimento sulla tua logica aziendale. Come tale, quando annoti una proprietà con [NotNull], ti assumi la responsabilità del tuo impegno che "questo non accadrà mai poiché Idè un PK e non può mai essere nullo".

Per aiutarti a mantenere tale impegno, potresti anche voler annotare la proprietà con l' [DisallowNull]attributo:

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

Riferimento: secondo la documentazione , l' [DisallowNull]attributo "specifica che nullnon è consentito come input anche se il tipo corrispondente lo consente".

Questo potrebbe non essere pertinente nel tuo caso poiché i valori vengono assegnati tramite il database, ma l' [DisallowNull]attributo ti darà un avvertimento se tenti di assegnare un nullvalore (in grado) a Id, anche se il tipo restituito consente altrimenti di essere null . A tale proposito, Idavrebbe agito esattamente come un stringquanto riguarda l'analisi statica del flusso C # s 'è interessato, mentre anche consentendo il valore di rimanere inizializzata tra la costruzione dell'oggetto e la popolazione della proprietà.

Nota: come altri hanno già detto, è anche possibile ottenere un risultato praticamente identico assegnando Idun valore predefinito di uno default!o null!. Questa è, certamente, una sorta di preferenza stilistica. Preferisco usare le annotazioni di nullability poiché sono più esplicite e forniscono un controllo granulare, mentre è facile abusare di !come un modo per chiudere il compilatore. L'inizializzazione esplicita di una proprietà con un valore mi assilla anche se so che non userò mai quel valore, anche se è l'impostazione predefinita.


-2

String è un tipo di riferimento e sempre nullable, non è necessario fare nulla di speciale. Potresti avere problemi solo in seguito se desideri mappare questo tipo di oggetto a un altro, ma puoi gestirlo in seguito.


8
Sta parlando dei tipi di riferimento Nullable in C # 8
Magnus
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.