Bagikan melalui


Tutorial: Ekspresikan niat desain Anda dengan lebih jelas menggunakan referensi nullable dan non-nullable.

Jenis referensi nullable melengkapi jenis referensi dengan cara yang sama seperti jenis nilai nullable melengkapi jenis nilai. Anda mendeklarasikan variabel menjadi jenis referensi nullable dengan menambahkan ? ke tipe. Misalnya, string? mewakili nullable string. Anda dapat menggunakan jenis baru ini untuk lebih mengekspresikan niat desain Anda: beberapa variabel harus selalu memiliki nilai sementara yang lain mungkin kehilangan nilai.

Dalam tutorial ini, Anda akan belajar cara:

  • Menggabungkan jenis referensi nullable dan non-nullable ke dalam desain Anda
  • Aktifkan pemeriksaan jenis referensi nullable di seluruh kode Anda.
  • Tulis kode di mana kompilator memberlakukan keputusan desain tersebut.
  • Gunakan fitur referensi nullable dalam desain Anda sendiri

Prasyarat

Tutorial ini mengasumsikan Anda terbiasa dengan C# dan .NET, termasuk Visual Studio atau .NET CLI.

Sertakan tipe referensi nullable dalam desain Anda

Dalam tutorial ini, Anda akan membangun pustaka yang memodelkan menjalankan survei. Kode ini menggunakan jenis referensi nullable dan jenis referensi non-nullable untuk mewakili konsep dunia nyata. Pertanyaan survei tidak pernah bisa kosong. Responden mungkin lebih suka tidak menjawab pertanyaan. Responsnya mungkin dalam kasus ini null.

Kode yang Anda tulis untuk sampel ini mengekspresikan niat tersebut, dan pengkompilasi memberlakukan niat tersebut.

Membuat aplikasi dan mengaktifkan jenis referensi nullable

Buat aplikasi konsol baru baik di Visual Studio atau dari baris perintah menggunakan dotnet new console. Beri nama aplikasi NullableIntroduction. Setelah membuat aplikasi, Anda perlu menentukan bahwa seluruh proyek dikompilasi dalam konteks anotasi nullable yang diaktifkan. Buka file .csproj dan tambahkan Nullable elemen ke PropertyGroup elemen . Atur nilainya ke enable. Anda harus mengaktifkan fitur jenis referensi nullable dalam proyek yang dibuat sebelum C# 11 / .NET 7. Setelah fitur diaktifkan, deklarasi variabel referensi yang ada menjadi jenis referensi yang tidak dapat diubah ke null. Meskipun keputusan tersebut membantu menemukan masalah di mana kode yang ada mungkin tidak memiliki pemeriksaan null yang tepat, keputusan tersebut mungkin tidak secara akurat mencerminkan niat desain asli Anda:

<Nullable>enable</Nullable>

Merancang jenis untuk aplikasi

Aplikasi survei ini mengharuskan pembuatan kelas-kelas ini:

  • Kelas yang memodelkan daftar pertanyaan.
  • Kelas yang memodelkan daftar orang yang dihubungi untuk survei.
  • Kelas yang memodelkan jawaban dari seseorang yang mengikuti survei.

Jenis ini menggunakan jenis referensi nullable dan non-nullable untuk mengekspresikan anggota mana yang diperlukan dan anggota mana yang bersifat opsional. Jenis referensi nullable mengkomunikasikan niat desain dengan jelas:

  • Pertanyaan yang merupakan bagian dari survei tidak boleh kosong: Tidak logis untuk mengajukan pertanyaan kosong.
  • Responden tidak pernah bisa null. Anda ingin melacak orang yang Anda hubungi, bahkan responden yang menolak untuk berpartisipasi.
  • Setiap respons terhadap pertanyaan mungkin null. Responden dapat menolak menjawab beberapa atau semua pertanyaan.

Anda mungkin begitu terbiasa dengan tipe referensi yang memungkinkan nilai null sehingga Anda mungkin melewatkan peluang lain untuk mendeklarasikan instans yang tidak dapat bernilai null:

  • Kumpulan pertanyaan harus tidak boleh bernilai null.
  • Koleksi responden harus tidak dapat bernilai null.

Saat Anda menulis kode, Anda melihat bahwa jenis referensi yang tidak dapat diubah ke null sebagai default untuk referensi menghindari kesalahan umum yang dapat menyebabkan NullReferenceExceptions. Salah satu pelajaran dari tutorial ini adalah bahwa Anda membuat keputusan tentang variabel mana yang bisa atau tidak bisa null. Bahasa ini tidak menyediakan sintaks untuk mengekspresikan keputusan tersebut. Sekarang itu terjadi.

Aplikasi yang Anda buat melakukan langkah-langkah berikut:

  1. Membuat survei dan menambahkan pertanyaan ke dalamnya.
  2. Membuat sekumpulan responden pseudo-random untuk survei.
  3. Menghubungi responden hingga ukuran survei yang selesai mencapai jumlah yang ditargetkan.
  4. Menulis statistik penting tentang respons survei.

Membangun survei dengan jenis referensi nullable dan jenis referensi yang tidak dapat bernilai null.

Kode pertama yang Anda tulis membuat survei. Anda menulis kelas untuk memodelkan pertanyaan survei dan menjalankan survei. Survei Anda memiliki tiga jenis pertanyaan, dibedakan dengan format jawaban: Jawaban Ya/Tidak, jawaban angka, dan jawaban teks. Buat public SurveyQuestion class:

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

Pengompilasi menginterpretasikan setiap deklarasi variabel jenis referensi sebagai jenis referensi yang tidak dapat diubah ke null untuk kode dalam konteks anotasi null yang diaktifkan. Anda dapat melihat peringatan pertama Anda dengan menambahkan properti untuk teks pertanyaan dan jenis pertanyaan, seperti yang ditunjukkan dalam kode berikut:

namespace NullableIntroduction
{
    public enum QuestionType
    {
        YesNo,
        Number,
        Text
    }

    public class SurveyQuestion
    {
        public string QuestionText { get; }
        public QuestionType TypeOfQuestion { get; }
    }
}

Karena Anda tidak menginisialisasi QuestionText, pengkompilasi mengeluarkan peringatan bahwa properti yang tidak dapat diubah ke null tidak diinisialisasi. Desain Anda mengharuskan teks pertanyaan tidak boleh null, jadi Anda menambahkan konstruktor untuk menginisialisasi teks pertanyaan tersebut beserta nilai QuestionType. Definisi kelas yang sudah selesai terlihat seperti kode berikut:

namespace NullableIntroduction;

public enum QuestionType
{
    YesNo,
    Number,
    Text
}

public class SurveyQuestion
{
    public string QuestionText { get; }
    public QuestionType TypeOfQuestion { get; }

    public SurveyQuestion(QuestionType typeOfQuestion, string text) =>
        (TypeOfQuestion, QuestionText) = (typeOfQuestion, text);
}

Menambahkan konstruktor akan menghapus peringatan. Argumen konstruktor juga merupakan jenis referensi yang tidak dapat diubah ke null, sehingga pengkompilasi tidak mengeluarkan peringatan apa pun.

Selanjutnya, buat public kelas bernama SurveyRun. Kelas ini berisi daftar SurveyQuestion objek dan metode untuk menambahkan pertanyaan ke survei, seperti yang ditunjukkan dalam kode berikut:

using System.Collections.Generic;

namespace NullableIntroduction
{
    public class SurveyRun
    {
        private List<SurveyQuestion> surveyQuestions = new List<SurveyQuestion>();

        public void AddQuestion(QuestionType type, string question) =>
            AddQuestion(new SurveyQuestion(type, question));
        public void AddQuestion(SurveyQuestion surveyQuestion) => surveyQuestions.Add(surveyQuestion);
    }
}

Seperti sebelumnya, Anda harus menginisialisasi objek daftar ke nilai non-null atau pengkompilasi mengeluarkan peringatan. Tidak ada pemeriksaan null dalam kelebihan beban AddQuestion kedua karena pengkompilasi membantu memberlakukan kontrak yang tidak dapat diubah ke null: Anda menyatakan variabel tersebut tidak dapat diubah ke null. Meskipun kompilator memperingatkan tentang potensi penetapan nilai null, nilai null pada waktu berjalan masih mungkin terjadi. Untuk API publik, pertimbangkan untuk menambahkan validasi argumen bahkan untuk jenis referensi yang tidak dapat diubah ke null, karena kode klien mungkin tidak mengaktifkan jenis referensi yang dapat diubah ke null atau sengaja meneruskan null.

Beralih ke Program.cs di editor Anda dan ganti konten Main dengan baris kode berikut:

var surveyRun = new SurveyRun();
surveyRun.AddQuestion(QuestionType.YesNo, "Has your code ever thrown a NullReferenceException?");
surveyRun.AddQuestion(new SurveyQuestion(QuestionType.Number, "How many times (to the nearest 100) has that happened?"));
surveyRun.AddQuestion(QuestionType.Text, "What is your favorite color?");

Karena seluruh proyek berada dalam konteks anotasi nullable yang diaktifkan, Anda akan mendapatkan peringatan ketika meneruskan null ke metode mana pun yang mengharapkan tipe referensi yang tidak dapat bernilai null. Cobalah dengan menambahkan baris berikut ke Main:

surveyRun.AddQuestion(QuestionType.Text, default);

Membuat responden dan mendapatkan jawaban atas survei

Selanjutnya, tulis kode yang menghasilkan jawaban atas survei. Proses ini melibatkan beberapa tugas kecil:

  1. Buat metode yang menghasilkan objek responden. Objek-objek ini mewakili orang-orang yang diminta untuk mengisi survei.
  2. Bangun logika untuk mensimulasikan mengajukan pertanyaan kepada responden dan mengumpulkan jawaban atau mencatat bahwa responden tidak menjawab.
  3. Ulangi hingga responden yang cukup menjawab survei.

Anda memerlukan kelas untuk mewakili respons survei, jadi tambahkan sekarang. Aktifkan nullable support. Id Tambahkan properti dan konstruktor yang menginisialisasinya, seperti yang ditunjukkan dalam kode berikut:

namespace NullableIntroduction
{
    public class SurveyResponse
    {
        public int Id { get; }

        public SurveyResponse(int id) => Id = id;
    }
}

Selanjutnya, tambahkan static metode untuk membuat peserta baru dengan membuat ID acak:

private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());

Tanggung jawab utama kelas ini adalah untuk menghasilkan respons bagi peserta terhadap pertanyaan dalam survei. Tanggung jawab ini memiliki beberapa langkah:

  1. Minta partisipasi dalam survei. Jika orang tersebut tidak menyetujui, kembalikan respons kosong (atau null).
  2. Ajukan setiap pertanyaan dan catat jawabannya. Setiap jawaban mungkin juga hilang (atau null).

Tambahkan kode berikut ke kelas SurveyResponse Anda:

private Dictionary<int, string>? surveyResponses;
public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
    if (ConsentToSurvey())
    {
        surveyResponses = new Dictionary<int, string>();
        int index = 0;
        foreach (var question in questions)
        {
            var answer = GenerateAnswer(question);
            if (answer != null)
            {
                surveyResponses.Add(index, answer);
            }
            index++;
        }
    }
    return surveyResponses != null;
}

private bool ConsentToSurvey() => randomGenerator.Next(0, 2) == 1;

private string? GenerateAnswer(SurveyQuestion question)
{
    switch (question.TypeOfQuestion)
    {
        case QuestionType.YesNo:
            int n = randomGenerator.Next(-1, 2);
            return (n == -1) ? default : (n == 0) ? "No" : "Yes";
        case QuestionType.Number:
            n = randomGenerator.Next(-30, 101);
            return (n < 0) ? default : n.ToString();
        case QuestionType.Text:
        default:
            switch (randomGenerator.Next(0, 5))
            {
                case 0:
                    return default;
                case 1:
                    return "Red";
                case 2:
                    return "Green";
                case 3:
                    return "Blue";
            }
            return "Red. No, Green. Wait.. Blue... AAARGGGGGHHH!";
    }
}

Penyimpanan untuk jawaban survei adalah Dictionary<int, string>?, yang menunjukkan bahwa itu mungkin null. Anda menggunakan fitur bahasa baru untuk mendeklarasikan niat desain Anda, baik kepada pengkompilasi maupun kepada siapa pun yang membaca kode Anda nanti. Jika Anda mendereferensi surveyResponses tanpa terlebih dahulu memeriksa nilai null, Anda akan mendapatkan peringatan kompilator. Anda tidak mendapatkan peringatan dalam AnswerSurvey metode karena pengkompilasi dapat menentukan surveyResponses variabel diatur ke nilai non-null dalam kode sebelumnya.

Menggunakan null untuk jawaban yang hilang menyoroti titik kunci untuk bekerja dengan jenis referensi yang dapat diubah ke null: tujuan Anda bukan untuk menghapus semua null nilai dari program Anda. Sebaliknya, tujuan Anda adalah memastikan bahwa kode yang Anda tulis mengekspresikan niat desain Anda. Nilai yang hilang adalah konsep yang diperlukan untuk diekspresikan dalam kode Anda. Nilainya null adalah cara yang jelas untuk mengekspresikan nilai yang hilang tersebut. Mencoba menghapus semua null nilai menyebabkan menentukan beberapa cara lain untuk mengekspresikan nilai yang hilang tanpa null.

Selanjutnya, Anda perlu menulis metode PerformSurvey di kelas SurveyRun. Tambahkan kode berikut di SurveyRun kelas :

private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
    int respondentsConsenting = 0;
    respondents = new List<SurveyResponse>();
    while (respondentsConsenting < numberOfRespondents)
    {
        var respondent = SurveyResponse.GetRandomId();
        if (respondent.AnswerSurvey(surveyQuestions))
            respondentsConsenting++;
        respondents.Add(respondent);
    }
}

Di sini lagi, pilihan Anda untuk nilai List<SurveyResponse>? yang dapat bernilai null menunjukkan responsnya mungkin null. Itu menunjukkan survei belum diberikan kepada responden mana pun. Perhatikan bahwa responden ditambahkan sampai persetujuan yang cukup terkumpul.

Langkah terakhir untuk menjalankan survei adalah menambahkan panggilan untuk melakukan survei di akhir Main metode:

surveyRun.PerformSurvey(50);

Memeriksa tanggapan survei

Langkah terakhir adalah menampilkan hasil survei. Anda menambahkan kode ke banyak kelas yang Anda tulis. Kode ini menunjukkan nilai membedakan jenis referensi nullable dan non-nullable. Mulailah dengan menambahkan dua anggota berbentuk ekspresi berikut ke kelas SurveyResponse.

public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";

Karena surveyResponses merupakan jenis referensi nullable, pemeriksaan null diperlukan sebelum membatalkan referensi. Metode Answer mengembalikan string yang tidak dapat bernilai null, jadi kita harus mengatasi kasus jawaban yang hilang dengan menggunakan operator null-coalescing.

Selanjutnya, tambahkan tiga anggota bertubuh ekspresi ini ke SurveyRun kelas :

public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];

Anggota AllParticipants harus memperhitungkan bahwa variabel respondents dapat null, tetapi nilai yang dikembalikan tidak boleh null. Jika Anda mengubah ekspresi tersebut dengan menghapus ?? dan urutan kosong yang mengikutinya, pengompilasi memperingatkan Anda bahwa metode tersebut mungkin mengembalikan null dan tanda tangan pengembaliannya mengembalikan jenis yang tidak dapat bernilai null.

Terakhir, tambahkan perulangan berikut di bagian bawah metode Main.

foreach (var participant in surveyRun.AllParticipants)
{
    Console.WriteLine($"Participant: {participant.Id}:");
    if (participant.AnsweredSurvey)
    {
        for (int i = 0; i < surveyRun.Questions.Count; i++)
        {
            var answer = participant.Answer(i);
            Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} : {answer}");
        }
    }
    else
    {
        Console.WriteLine("\tNo responses");
    }
}

Anda tidak memerlukan pemeriksaan apa pun null dalam kode ini karena Anda merancang antarmuka yang mendasar sehingga semuanya mengembalikan jenis referensi yang tidak dapat diubah ke null. Analisis statis kompilator membantu memastikan kontrak desain ini diikuti.

Dapatkan kode

Anda bisa mendapatkan kode untuk tutorial yang sudah selesai dari repositori sampel kami di folder csharp/NullableIntroduction .

Melakukan eksperimen dengan mengubah deklarasi jenis antara jenis referensi nullable dan non-nullable. Lihat bagaimana itu menghasilkan peringatan yang berbeda untuk memastikan Anda tidak secara tidak sengaja mendereferensikan null.

Langkah berikutnya

Pelajari cara menggunakan jenis referensi nullable saat menggunakan Entity Framework: