Najlepsza praktyka dotycząca używania dopuszczalnych typów odniesienia dla DTO

20

Mam DTO, który jest wypełniany przez czytanie z tabeli DynamoDB. Powiedzmy, że obecnie wygląda to tak:

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
}

Czy istnieją jakieś najlepsze praktyki dotyczące radzenia sobie z tym? Wolałbym unikać nieparametrycznego konstruktora, ponieważ źle działa on z ORM w zestawie Dynamo SDK (podobnie jak inne).

Wydaje mi się dziwne pisanie, public string Id { get; set; } = "";ponieważ to się nigdy nie wydarzy, ponieważ Idjest to PK i nigdy nie może być zerowe. Jaki byłby użytek, ""gdyby tak się stało?

Więc jakaś najlepsza praktyka w tym zakresie?

  • Czy powinienem zaznaczyć je wszystkie, string?aby powiedzieć, że mogą być zerowe, chociaż niektórzy nigdy nie powinni.
  • Powinienem zainicjować Idi za Namepomocą, ""ponieważ nigdy nie powinny być zerowe, a to pokazuje zamiar, chociaż ""nigdy nie zostanie użyty.
  • Niektóre kombinacje powyższych

Uwaga: chodzi o typy zerowalne C # 8. Jeśli nie wiesz, co najlepiej, nie odpowiadaj.

BritishDeveloper
źródło
Jest trochę brudny, ale możesz po prostu uderzyć #pragma warning disable CS8618w górną część pliku.
20
7
Zamiast tego = ""możesz użyć = null!do zainicjowania właściwości, o której wiesz, że nigdy tak naprawdę nie będzie null(gdy kompilator nie ma możliwości, aby to wiedzieć). Jeśli jest Descriptionto zgodne z prawem null, należy je zadeklarować string?. Alternatywnie, jeśli sprawdzanie zerowości dla DTO jest bardziej uciążliwe niż pomoc, możesz po prostu owinąć ten typ w #nullable disable/, #nullable restoreaby wyłączyć NRT tylko dla tego typu.
Jeroen Mostert,
@JeroenMostert Powinieneś podać to jako odpowiedź.
Magnus,
3
@Magnus: Niechętnie odpowiadam na pytania z prośbą o „najlepsze praktyki”; takie rzeczy są szerokie i subiektywne. Mam nadzieję, że PO wykorzysta mój komentarz do opracowania własnej „najlepszej praktyki”.
Jeroen Mostert,
1
@ IvanGarcíaTopete: Chociaż zgadzam się, że użycie ciągu jako klucza podstawowego jest niezwykłe, a nawet może być niewskazane w zależności od okoliczności, wybór typu danych przez OP jest dość nieistotny dla pytania. To może równie dobrze zastosować się do wymaganej, niepozwalającej na własność właściwości łańcucha, która nie jest kluczem podstawowym, a nawet pola łańcucha, które jest częścią złożonego klucza podstawowego, a pytanie nadal będzie ważne.
Jeremy Caney

Odpowiedzi:

12

Opcjonalnie możesz użyć defaultliterału w połączeniu znull forgiving operator

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

Ponieważ Twoje DTO jest zapełniane z DynamoDB, możesz użyć MaybeNull/NotNull atrybutów warunków dodatkowych, aby kontrolować nullability

  • MaybeNull Zwracana wartość zerowa może być zerowa.
  • NotNull Zwracana zerowa wartość nigdy nie będzie pusta.

Ale te atrybuty wpływają tylko na analizę zerowalną dla dzwoniących członków, którzy są z nimi opatrzeni adnotacjami. Zazwyczaj te atrybuty stosuje się do zwrotów metod, właściwości i modułów pobierających indeksatory.

Możesz więc rozważyć wszystkie swoje właściwości, które nie mają wartości zerowej, i udekoruj je MaybeNullatrybutem, wskazując, że zwracają one możliwą nullwartość

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

Poniższy przykład pokazuje użycie zaktualizowanej Itemklasy. Jak widać, druga linia nie wyświetla ostrzeżenia, ale trzecia tak

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.

Lub możesz NoNullustawić wszystkie właściwości na wartości zerowe i użyć, aby wskazać, że zwracana wartość nie może być null( Idna przykład)

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

Ostrzeżenie będzie takie samo jak w poprzednim przykładzie.

Istnieją również AllowNull/DisallowNull atrybuty warunków wstępnych dla parametrów wejściowych, właściwości i ustawiaczy indeksujących, działających w podobny sposób.

  • AllowNull Niedopuszczalny argument wejściowy może mieć wartość NULL.
  • DisallowNull Argument wejściowy o wartości zerowej nigdy nie powinien mieć wartości NULL.

Nie sądzę, żeby ci to pomogło, ponieważ twoja klasa jest zapełniona z bazy danych, ale możesz ich użyć do kontrolowania zerowania właściwości ustawiających właściwości, tak jak dla pierwszej opcji

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

I po drugie

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

Niektóre pomocne szczegóły i przykłady postów / warunków wstępnych można znaleźć w tym artykule na blogu

Pavel Anikhouski
źródło
6

Odpowiedzią podręcznika w tym scenariuszu jest użycie string?dla twojej Idnieruchomości, ale także udekorowanie jej [NotNull]atrybutem:

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

Numer referencyjny: Zgodnie z dokumentacją , [NotNull]atrybut „oznacza, że wyjście nie jest null, nawet jeśli odpowiedni typ na to pozwala.”

Co się tu właściwie dzieje?

  1. Po pierwsze, string?typ zwracany uniemożliwia kompilatorowi ostrzeżenie, że właściwość nie jest zainicjowana podczas budowy i dlatego domyślnie jest ustawiona na null.
  2. Następnie [NotNull]atrybut zapobiega ostrzeżeniu podczas przypisywania właściwości do zmiennej, która nie ma wartości zerowej lub próby jej wyrejestrowania, ponieważ informujesz analizę statycznego przepływu kompilatora, że w praktyce ta właściwość nigdy nie będzie null.

Ostrzeżenie: Jak we wszystkich przypadkach dotyczących kontekstu dopuszczalności C #, technicznie nic nie stoi na przeszkodzie, abyś nadal zwracał tu nullwartość, a tym samym potencjalnie mógł wprowadzić pewne wyjątki; tzn. nie ma gotowej do sprawdzenia poprawności środowiska wykonawczego. Wszystko, co zapewnia C #, to ostrzeżenie kompilatora. Kiedy się wprowadzasz [NotNull], skutecznie zastępujesz to ostrzeżenie, dając mu wskazówkę dotyczącą logiki biznesowej. W związku z tym, kiedy adnotujesz nieruchomość [NotNull], bierzesz odpowiedzialność za swoje zobowiązanie, że „to się nigdy nie wydarzy, ponieważ Idjest to PK i nigdy nie może być zerowe”.

Aby pomóc Ci utrzymać to zobowiązanie, możesz dodatkowo dodać adnotację do właściwości za pomocą [DisallowNull]atrybutu:

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

Numer referencyjny: Zgodnie z dokumentacją , gdy [DisallowNull]atrybut „określa, że nulljest niedozwolone jako wejście nawet jeśli odpowiedni typ na to pozwala.”

Może to nie być istotne w twoim przypadku, ponieważ wartości są przypisywane za pośrednictwem bazy danych, ale ten [DisallowNull]atrybut wyświetli ostrzeżenie, jeśli kiedykolwiek spróbujesz przypisać null(zdolną) wartość do Id, nawet jeśli typ zwracany pozwala inaczej zero . W tym względzie Idbędzie działać dokładnie jak stringw miarę statycznej analizy przepływowej C # 's jest zaniepokojony, a także pozwalając wartość pozostać niezainicjowanymi między budową obiektu i ludności nieruchomości.

Uwaga: Jak wspominają inni, można również osiągnąć praktycznie identyczny wynik, przypisując Idwartość domyślna: albo default!albo null!. Jest to wprawdzie nieco preferencja stylistyczna. Wolę używać adnotacji o zerowaniu, ponieważ są one bardziej wyraźne i zapewniają szczegółową kontrolę, podczas gdy łatwo można nadużywać !jako sposobu zamykania kompilatora. Jawne inicjowanie właściwości wartością również mnie wkurza, jeśli wiem, że nigdy nie użyję tej wartości - nawet jeśli jest to wartość domyślna.

Jeremy Caney
źródło
-2

String jest typem referencyjnym i zawsze ma wartość null, nie musisz robić nic specjalnego. Możesz mieć problemy tylko później, jeśli chcesz zmapować ten typ obiektu na inny, ale możesz sobie z tym poradzić później.

dino
źródło
8
Mówi o typach odniesienia zerowalnych w C # 8
Magnus