Best Practice für die Verwendung von nullbaren Referenztypen für DTOs

20

Ich habe ein DTO, das durch Lesen aus einer DynamoDB-Tabelle gefüllt wird. Angenommen, es sieht derzeit so aus:

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
}

Gibt es bewährte Verfahren, um damit umzugehen? Ich würde lieber einen nicht parameterlosen Konstruktor vermeiden, da dies mit dem ORM im Dynamo SDK (und anderen) schlecht funktioniert.

Es scheint mir seltsam zu schreiben, public string Id { get; set; } = "";weil dies niemals passieren wird , da Ides sich um eine PK handelt und niemals null sein kann. Was würde ""es nützen , selbst wenn es irgendwie wäre?

Also eine Best Practice dazu?

  • Sollte ich sie alle markieren, um string?zu sagen, dass sie null sein können, obwohl einige es niemals sein sollten.
  • Soll ich initialisieren Idund Namemit , ""weil sie sollte nie sogar null und zeigt dies die Absicht sein , obwohl ""nie verwendet werden.
  • Eine Kombination von oben

Bitte beachten Sie: Hierbei handelt es sich um nullbare C # 8- Referenztypen. Wenn Sie nicht wissen, welche am besten geeignet sind, antworten Sie nicht.

Britischer Entwickler
quelle
Es ist ein wenig schmutzig, aber Sie können einfach #pragma warning disable CS8618oben in die Datei klatschen.
Zwanzig
7
Stattdessen = ""können Sie = null!eine Eigenschaft initialisieren, von der Sie wissen, dass sie niemals effektiv sein wird null(wenn der Compiler keine Möglichkeit hat, dies zu wissen). Wenn Descriptiones legal sein kann null, sollte es als a deklariert werden string?. Wenn die Überprüfung der Nullfähigkeit für das DTO eher störend als hilfreich ist, können Sie den Typ alternativ einfach in #nullable disable/ #nullable restoreumbrechen, um NRTs nur für diesen Typ zu deaktivieren.
Jeroen Mostert
@JeroenMostert Das solltest du als Antwort setzen.
Magnus
3
@Magnus: Ich beantworte nur ungern Fragen, die nach "Best Practices" fragen. solche Dinge sind breit und subjektiv. Ich hoffe, dass das OP meinen Kommentar nutzen kann, um seine eigenen "Best Practice" zu entwickeln.
Jeroen Mostert
1
@ IvanGarcíaTopete: Obwohl ich der Meinung bin, dass die Verwendung einer Zeichenfolge für einen Primärschlüssel ungewöhnlich ist und je nach den Umständen sogar nicht empfohlen werden kann, ist die Wahl des Datentyps durch das OP für die Frage ziemlich irrelevant. Dies könnte genauso gut auf eine erforderliche, nicht nullfähige Zeichenfolgeeigenschaft angewendet werden, die nicht der Primärschlüssel ist, oder sogar auf ein Zeichenfolgenfeld, das Teil eines zusammengesetzten Primärschlüssels ist, und die Frage würde weiterhin bestehen bleiben.
Jeremy Caney

Antworten:

12

Optional können Sie das defaultLiteral in Kombination mit dem verwendennull forgiving operator

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

Da Ihr DTO aus DynamoDB gefüllt ist, können Sie MaybeNull/NotNull Nachbedingungsattribute verwenden , um die Nullfähigkeit zu steuern

  • MaybeNull Ein nicht nullbarer Rückgabewert kann null sein.
  • NotNull Ein nullbarer Rückgabewert ist niemals null.

Diese Attribute wirken sich jedoch nur auf die Nullwertanalyse für die Anrufer von Mitgliedern aus, die mit Anmerkungen versehen sind. In der Regel wenden Sie diese Attribute auf Methodenrückgaben, Eigenschaften und Indexer-Getter an.

Sie können also alle Ihre Eigenschaften als nicht nullwertfähig betrachten und sie mit MaybeNullAttributen dekorieren, um anzuzeigen, dass sie einen möglichen nullWert zurückgeben

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

Das folgende Beispiel zeigt die Verwendung der aktualisierten ItemKlasse. Wie Sie sehen können, zeigt die zweite Zeile keine Warnung, die dritte jedoch

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.

Oder Sie können alle Eigenschaften auf Null setzen und NoNulldamit angeben, dass der Rückgabewert nicht sein kann null( Idzum Beispiel).

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

Die Warnung ist dieselbe wie im vorherigen Beispiel.

Es gibt auch AllowNull/DisallowNull Vorbedingungsattribute für Eingabeparameter, Eigenschaften und Indexer-Setter, die auf ähnliche Weise arbeiten.

  • AllowNull Ein nicht nullbares Eingabeargument kann null sein.
  • DisallowNull Ein nullbares Eingabeargument sollte niemals null sein.

Ich denke nicht, dass es Ihnen helfen wird, da Ihre Klasse aus der Datenbank gefüllt ist, aber Sie können sie verwenden, um die Nullbarkeit von Eigenschaftensetzern zu steuern, wie dies für die erste Option der Fall ist

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

Und für den zweiten

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

Einige hilfreiche Details und Beispiele für Post / Voraussetzungen finden Sie in diesem Devblog-Artikel

Pavel Anikhouski
quelle
6

Die Lehrbuchantwort in diesem Szenario besteht darin, ein string?für Ihre IdImmobilie zu verwenden, es aber auch mit dem [NotNull]Attribut zu dekorieren :

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

Referenz: Gemäß der Dokumentation gibt das [NotNull]Attribut "an, dass eine Ausgabe nicht null ist, selbst wenn der entsprechende Typ dies zulässt".

Also, was genau ist hier los?

  1. Erstens, die string?verhindert , dass Rückgabetyp der Compiler Sie warnen , dass die Eigenschaft während der Bauphase nicht initialisiert ist und somit standardmäßig zu null.
  2. Dann [NotNull]verhindert das Attribut eine Warnung, wenn die Eigenschaft einer nicht nullbaren Variablen zugewiesen wird oder versucht wird, sie zu dereferenzieren, da Sie die statische Flussanalyse des Compilers darüber informieren , dass diese Eigenschaft in der Praxis niemals sein wird null.

Warnung: Wie in allen Fällen, in denen der Nullfähigkeitskontext von C # eine Rolle spielt, hindert Sie nichts technisch daran, hier noch einen nullWert zurückzugeben und möglicherweise einige nachgelagerte Ausnahmen einzuführen. Das heißt, es gibt keine sofort einsatzbereite Laufzeitüberprüfung. Alles, was C # jemals bietet, ist eine Compiler-Warnung. Wenn Sie sich vorstellen [NotNull], überschreiben Sie diese Warnung effektiv, indem Sie einen Hinweis auf Ihre Geschäftslogik geben. Wenn Sie eine Eigenschaft mit kommentieren [NotNull], übernehmen Sie die Verantwortung für Ihre Verpflichtung, dass "dies niemals passieren wird , da Ides sich um eine PK handelt und niemals null sein kann".

Um diese Verpflichtung aufrechtzuerhalten, möchten Sie die Eigenschaft möglicherweise zusätzlich mit dem folgenden [DisallowNull]Attribut versehen:

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

Referenz: Gemäß der Dokumentation gibt das [DisallowNull]Attribut "an, dass nulleine Eingabe nicht zulässig ist, auch wenn der entsprechende Typ dies zulässt".

Dies ist in Ihrem Fall möglicherweise nicht relevant, da die Werte über die Datenbank zugewiesen werden. Das [DisallowNull]Attribut gibt jedoch eine Warnung aus, wenn Sie jemals versuchen, einen null(fähigen) Wert zuzuweisen Id, obwohl der Rückgabetyp dies ansonsten zulässt null . In dieser Hinsicht Idwürde sich genau wie bei der stringstatischen Flussanalyse von C # verhalten, während gleichzeitig der Wert zwischen der Konstruktion des Objekts und der Grundgesamtheit des Grundstücks nicht initialisiert werden kann.

Hinweis: Wie bereits erwähnt, können Sie auch ein praktisch identisches Ergebnis erzielen, indem Sie den IdStandardwert entweder default!oder zuweisen null!. Dies ist zugegebenermaßen eine stilistische Präferenz. Ich bevorzuge es, die Annotationen zur Nullbarkeit zu verwenden, da sie expliziter sind und eine differenzierte Steuerung bieten, während es leicht ist, sie zu missbrauchen, !um den Compiler herunterzufahren . Das explizite Initialisieren einer Eigenschaft mit einem Wert nervt mich auch, wenn ich weiß, dass ich diesen Wert niemals verwenden werde - selbst wenn dies der Standardwert ist.

Jeremy Caney
quelle
-2

String ist ein Referenztyp und immer nullbar. Sie müssen nichts Besonderes tun. Sie könnten erst später Probleme haben, wenn Sie diesen Objekttyp einem anderen zuordnen möchten, aber Sie können dies später behandeln.

Dino
quelle
8
Er spricht über nullable Referenztypen in C # 8
Magnus