Bagikan melalui


Tutorial: Mengekspresikan niat desain Anda dengan lebih jelas dengan jenis referensi nullable dan non-nullable

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

Dalam tutorial ini, Anda akan mempelajari cara:

  • Menggabungkan jenis referensi nullable dan non-nullable ke dalam desain Anda
  • Aktifkan pemeriksaan jenis referensi yang dapat diubah ke null di seluruh kode Anda.
  • Tulis kode tempat pengompilasi memberlakukan keputusan desain tersebut.
  • Gunakan fitur referensi yang dapat diubah ke null dalam desain Anda sendiri

Prasyarat

Anda harus menyiapkan komputer untuk menjalankan .NET, termasuk pengkompilasi C#. Pengkompilasi C# tersedia dengan Visual Studio 2022, atau .NET SDK.

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

Menggabungkan jenis referensi nullable dan non-nullable ke dalam desain Anda

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

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

Membuat aplikasi dan mengaktifkan jenis referensi yang dapat diubah ke null

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

<Nullable>enable</Nullable>

Sebelum .NET 6, proyek baru tidak menyertakan Nullable elemen. Dimulai dengan .NET 6, proyek baru menyertakan elemen <Nullable>enable</Nullable> dalam file proyek.

Merancang jenis untuk aplikasi

Aplikasi survei ini memerlukan pembuatan sejumlah kelas:

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

Jenis-jenis ini akan menggunakan jenis referensi nullable dan non-nullable untuk mengekspresikan anggota mana yang diperlukan dan anggota mana yang opsional. Jenis referensi yang dapat diubah ke null mengkomunikasikan niat desain tersebut dengan jelas:

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

Jika Anda telah memprogram di C#, Anda mungkin sangat terbiasa dengan jenis referensi yang memungkinkan null nilai yang mungkin melewatkan peluang lain untuk mendeklarasikan instans yang tidak dapat diubah ke null:

  • Kumpulan pertanyaan harus tidak dapat diubah ke null.
  • Pengumpulan responden harus tidak dapat diubah ke null.

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

Aplikasi yang akan 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 nomor tujuan.
  4. Menulis statistik penting tentang respons survei.

Membangun survei dengan jenis referensi nullable dan non-nullable

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

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 belum menginisialisasi QuestionText, pengompilasi mengeluarkan peringatan bahwa properti yang tidak dapat diubah ke null belum diinisialisasi. Desain Anda mengharuskan teks pertanyaan menjadi non-null, sehingga Anda menambahkan konstruktor untuk menginisialisasinya beserta QuestionType nilainya. 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 pengompilasi 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 pengompilasi akan mengeluarkan peringatan. Tidak ada pemeriksaan null dalam kelebihan beban AddQuestion yang kedua karena tidak diperlukan: Anda telah menyatakan variabel tersebut tidak dapat diubah ke null. Nilainya tidak bisa 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 saat Anda meneruskan null ke metode apa pun yang mengharapkan jenis referensi yang tidak dapat diubah ke null. Coba tambahkan 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. Ini mewakili orang-orang yang diminta untuk mengisi survei.
  2. Buat logika untuk mensimulasikan mengajukan pertanyaan kepada responden dan mengumpulkan jawaban atau mencatat bahwa responden tidak menjawab.
  3. Ulangi hingga responden yang cukup telah menjawab survei.

Anda memerlukan kelas untuk mewakili respons survei, jadi tambahkan sekarang. Aktifkan dukungan yang dapat diubah ke null. Tambahkan Id properti dan konstruktor yang menginisialisasinya, seperti yang diperlihatkan dalam kode berikut:

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

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

Selanjutnya, tambahkan metode static 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. Meminta partisipasi dalam survei. Jika orang tersebut tidak menyetujui, kembalikan respons yang hilang (atau null).
  2. Ajukan setiap pertanyaan dan catat jawabannya. Setiap jawaban juga mungkin 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 pengompilasi maupun kepada siapa pun yang membaca kode Anda nanti. Jika Anda pernah melakukan dereferensi surveyResponses tanpa memeriksa nilai null terlebih dahulu, Anda akan mendapatkan peringatan pengompilasi. Anda tidak mendapatkan peringatan dalam metode AnswerSurvey karena pengompilasi dapat menentukan variabel surveyResponses diatur ke nilai non-null di atas.

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 nilai null 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. null Nilai adalah cara yang jelas untuk mengekspresikan nilai yang hilang tersebut. Mencoba menghapus semua null nilai hanya menyebabkan penentuan beberapa cara lain untuk mengekspresikan nilai yang hilang tanpa null.

Berikutnya, Anda perlu menuliskan PerformSurvey metode dalam SurveyRun kelas. Tambahkan kode berikut ke 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, pilihan Anda yang dapat diubah ke null List<SurveyResponse>? menunjukkan responsnya mungkin null. Itu menunjukkan survei belum diberikan kepada responden mana pun. Perhatikan bahwa responden yang ditambahkan sampai mencukupi telah menyetujui.

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

surveyRun.PerformSurvey(50);

Memeriksa respons survei

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

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

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

Selanjutnya, tambahkan ketiga anggota berekspresi ini ke SurveyRunkelas:

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

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

Terakhir, tambahkan perulangan berikut di bagian bawah Mainmetode:

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 null dalam kode ini karena Anda telah merancang antarmuka yang mendasar sehingga semuanya mengembalikan jenis referensi yang tidak dapat diubah ke null.

Mendapatkan kode

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

Eksperimen dengan mengubah deklarasi jenis antara jenis referensi nullable dan non-nullable. Lihat bagaimana itu menghasilkan peringatan yang berbeda untuk memastikan Anda tidak secara sengaja melakukan dereferensi terhadap null.

Langkah berikutnya

Pelajari cara menggunakan jenis referensi yang dapat diubah ke null saat menggunakan Kerangka Kerja Entitas: