Praktik terbaik untuk menggunakan Jenis Referensi Nullable untuk DTO

20

Saya memiliki DTO yang diisi dengan membaca dari tabel DynamoDB. Katakan seperti ini saat ini:

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
}

Apakah ada praktik terbaik yang dikembangkan untuk menangani ini? Saya lebih suka menghindari konstruktor non-parameterless karena itu bermain buruk dengan ORM di Dynamo SDK (dan juga yang lain).

Rasanya aneh bagi saya untuk menulis public string Id { get; set; } = "";karena ini tidak akan pernah terjadi karena Idini adalah PK dan tidak pernah bisa menjadi nol. Apa gunanya ""bahkan jika itu entah bagaimana caranya?

Jadi ada praktik terbaik dalam hal ini?

  • Haruskah saya menandai mereka semua string?dengan mengatakan bahwa mereka bisa nol meskipun beberapa tidak seharusnya.
  • Haruskah saya menginisialisasinya Iddan Namedengan ""karena mereka harus tidak pernah batal dan menunjukkan ini maksud meskipun ""tidak akan pernah digunakan.
  • Beberapa kombinasi di atas

Harap dicatat: ini tentang C # 8 jenis referensi yang tidak dapat dibatalkan Jika Anda tidak tahu apa yang sebaiknya tidak dijawab.

Pengembang Inggris
sumber
Agak kotor, tetapi Anda bisa menampar #pragma warning disable CS8618di bagian atas file.
Dua puluh
7
Daripada = "", Anda dapat menggunakan = null!untuk menginisialisasi properti yang Anda tahu tidak akan pernah efektif null(ketika kompiler tidak memiliki cara untuk mengetahui hal itu). Jika Descriptionsecara hukum dapat null, harus dinyatakan a string?. Atau, jika nullability memeriksa DTO lebih banyak gangguan daripada bantuan, Anda cukup membungkus jenis dalam #nullable disable/ #nullable restoreuntuk mematikan NRT untuk jenis ini saja.
Jeroen Mostert
@ JoeroMostert Anda harus meletakkan itu sebagai jawaban.
Magnus
3
@ Magnus: Saya enggan menjawab pertanyaan yang menanyakan "praktik terbaik"; hal-hal seperti itu luas dan subyektif. Saya berharap OP dapat menggunakan komentar saya untuk mengembangkan "praktik terbaik" mereka sendiri.
Jeroen Mostert
1
@ IvanGarcíaTopete: Meskipun saya setuju bahwa menggunakan string untuk kunci primer tidak biasa, dan bahkan mungkin tidak disarankan tergantung pada keadaan, pilihan tipe data OP sangat tidak relevan dengan pertanyaan. Ini bisa dengan mudah diterapkan pada properti string yang diperlukan dan tidak dapat dibatalkan yang bukan kunci utama, atau bahkan bidang string yang merupakan bagian dari kunci primer komposit, dan pertanyaannya akan tetap ada.
Jeremy Caney

Jawaban:

12

Sebagai opsi, Anda dapat menggunakan defaultliteral dalam kombinasi dengannull forgiving operator

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

Karena DTO Anda diisi dari DynamoDB, Anda dapat menggunakan MaybeNull/NotNull atribut postcondition untuk mengontrol nullability

  • MaybeNull Nilai pengembalian yang tidak dapat dibatalkan dapat berupa nol.
  • NotNull Nilai pengembalian yang tidak dapat dibatalkan tidak akan pernah menjadi nol.

Tetapi atribut ini hanya memengaruhi analisis nullable untuk penelepon anggota yang dianotasi dengannya. Biasanya, Anda menerapkan atribut ini untuk pengembalian metode, properti, dan pengambil pengindeks.

Jadi, Anda dapat mempertimbangkan semua properti Anda yang tidak dapat dibatalkan dan menghiasnya dengan MaybeNullatribut, yang menunjukkan mereka mengembalikan nullnilai yang mungkin

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

Contoh berikut menunjukkan penggunaan Itemkelas yang diperbarui . Seperti yang Anda lihat, baris kedua tidak menunjukkan peringatan, tetapi yang ketiga tidak

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.

Atau Anda dapat membuat semua properti menjadi nullable dan digunakan NoNulluntuk menunjukkan bahwa nilai pengembalian tidak dapat null( Idmisalnya)

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

Peringatan akan sama dengan contoh sebelumnya.

Ada juga AllowNull/DisallowNull atribut prasyarat untuk parameter input, properti dan setter pengindeks, bekerja dengan cara yang sama.

  • AllowNull Argumen input yang tidak dapat dibatalkan dapat berupa null.
  • DisallowNull Argumen masukan yang dapat dibatalkan tidak boleh nol.

Saya tidak berpikir itu akan membantu Anda, karena kelas Anda diisi dari database, tetapi Anda dapat menggunakannya untuk mengontrol nullability dari setter properti, seperti ini untuk opsi pertama

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

Dan untuk yang kedua

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

Beberapa detail bermanfaat dan contoh post / prasyarat dapat ditemukan di artikel devblog ini

Pavel Anikhouski
sumber
6

Jawaban buku teks dalam skenario ini adalah menggunakan a string?untuk Idproperti Anda , tetapi juga menghiasinya dengan [NotNull]atribut:

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

Referensi: Menurut dokumentasi , [NotNull]atribut "menentukan bahwa output tidak nol bahkan jika tipe yang sesuai memungkinkannya."

Jadi, apa sebenarnya yang terjadi di sini?

  1. Pertama, string?tipe kembali mencegah compiler dari peringatan Anda bahwa properti uninitialized selama konstruksi dan dengan demikian akan secara default untuk null.
  2. Kemudian, [NotNull]atribut mencegah peringatan ketika menetapkan properti ke variabel yang tidak dapat dibatalkan atau berusaha untuk menundanya karena Anda menginformasikan analisis aliran statis kompiler yang, dalam praktiknya , properti ini tidak akan pernah null.

Peringatan: Seperti halnya semua kasus yang melibatkan konteks nullability C #, tidak ada yang secara teknis menghentikan Anda dari masih mengembalikan nullnilai di sini dan dengan demikian, berpotensi, memperkenalkan beberapa pengecualian hilir; yaitu, tidak ada validasi runtime out-of-the-box. Semua C # pernah menyediakan adalah peringatan kompiler. Ketika Anda memperkenalkan [NotNull]Anda secara efektif mengesampingkan peringatan itu dengan memberinya petunjuk tentang logika bisnis Anda. Dengan demikian, ketika Anda membubuhi keterangan properti dengan [NotNull], Anda bertanggung jawab atas komitmen Anda bahwa "ini tidak akan pernah terjadi karena Idini adalah PK dan tidak pernah dapat menjadi nol."

Untuk membantu Anda mempertahankan komitmen itu, Anda juga dapat membuat anotasi properti dengan [DisallowNull]atribut:

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

Referensi: Menurut dokumentasi , [DisallowNull]atribut "menentukan yang nulltidak diizinkan sebagai input bahkan jika tipe yang sesuai mengizinkannya."

Ini mungkin tidak relevan dalam kasus Anda karena nilai ditugaskan melalui database, tetapi [DisallowNull]atribut akan memberi Anda peringatan jika Anda pernah mencoba untuk menetapkan nilai null(mampu) Id, meskipun tipe pengembalian sebaliknya memungkinkan untuk itu menjadi nol . Dalam hal itu, Idakan bertindak persis seperti stringsejauh analisis aliran statis C # yang bersangkutan, sementara juga memungkinkan nilai untuk tetap diinisialisasi antara konstruksi objek dan populasi properti.

Catatan: Seperti yang disebutkan orang lain, Anda juga dapat mencapai hasil yang hampir identik dengan menetapkan nilai Iddefault salah satu default!atau null!. Harus diakui, ini agak preferensi gaya. Saya lebih suka menggunakan anotasi nullability karena lebih eksplisit dan memberikan kontrol granular, sedangkan mudah untuk menyalahgunakan !sebagai cara mematikan kompiler. Secara eksplisit menginisialisasi properti dengan nilai juga mengganggu saya jika saya tahu saya tidak akan pernah menggunakan nilai itu — bahkan jika itu adalah default.

Jeremy Caney
sumber
-2

String adalah tipe referensi dan selalu dapat dibatalkan, Anda tidak perlu melakukan sesuatu yang istimewa. Anda bisa memiliki masalah hanya nanti jika Anda ingin memetakan jenis objek ini ke yang lain, tetapi Anda bisa mengatasinya nanti.

dino
sumber
8
Dia berbicara tentang jenis referensi Nullable di C # 8
Magnus