Latihan - Menerapkan strategi keamanan null

Selesai

Di unit sebelumnya, Anda belajar tentang menyatakan maksud nullability dalam kode. Di unit ini, Anda akan menerapkan apa yang telah Anda pelajari ke proyek C# yang ada.

Catatan

Modul ini menggunakan .NET CLI (Antarmuka Baris Perintah) dan Visual Studio Code untuk pengembangan lokal. Setelah menyelesaikan modul ini, Anda dapat menerapkan konsep menggunakan Visual Studio (Windows), Visual Studio untuk Mac (macOS), atau melanjutkan pengembangan menggunakan Visual Studio Code (Windows, Linux, & macOS).

Modul ini menggunakan .NET 6.0 SDK. Pastikan Anda telah menginstal .NET 6.0 dengan menjalankan perintah berikut di terminal pilihan Anda:

dotnet --list-sdks

Anda akan melihat output yang mirip seperti ini:

3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]

Pastikan bahwa versi yang dimulai dengan 6 terdaftar. Jika tidak ada yang terdaftar atau perintah tidak ditemukan, instal SDK .NET 6.0 terbaru.

Ambil dan periksa kode sampel

  1. Pada terminal perintah, kloning repositori GitHub sampel dan beralih ke direktori hasil kloning.

    git clone https://github.com/microsoftdocs/mslearn-csharp-null-safety
    cd mslearn-csharp-null-safety
    
  2. Buka direktori proyek di Visual Studio Code.

    code .
    
  3. Jalankan proyek contoh menggunakan perintah dotnet run.

    dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
    

    Ini akan menghasilkan NullReferenceException yang dilempar.

    dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
    Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
       at Program.<Main>$(String[] args) in .\src\ContosoPizza.Service\Program.cs:line 13
    

    Jejak tumpukan menunjukkan bahwa pengecualian terjadi pada baris 13 di .\src\ContosoPizza.Service\Program.cs. Pada baris 13, metode Add dipanggil pada properti pizza.Cheeses. Karena pizza.Cheeses adalah null, NullReferenceException dilemparkan.

    using ContosoPizza.Models;
    
    // Create a pizza
    Pizza pizza = new("Meat Lover's Special")
    {
        Size = PizzaSize.Medium,
        Crust = PizzaCrust.DeepDish,
        Sauce = PizzaSauce.Marinara,
        Price = 17.99m,
    };
    
    // Add cheeses
    pizza.Cheeses.Add(PizzaCheese.Mozzarella);
    pizza.Cheeses.Add(PizzaCheese.Parmesan);
    
    // Add toppings
    pizza.Toppings.Add(PizzaTopping.Sausage);
    pizza.Toppings.Add(PizzaTopping.Pepperoni);
    pizza.Toppings.Add(PizzaTopping.Bacon);
    pizza.Toppings.Add(PizzaTopping.Ham);
    pizza.Toppings.Add(PizzaTopping.Meatballs);
    
    Console.WriteLine(pizza);
    
    /*
        Expected output:
    
        The "Meat Lover's Special" is a deep dish pizza with marinara sauce.
        It's covered with a blend of mozzarella and parmesan cheese.
        It's layered with sausage, pepperoni, bacon, ham and meatballs.
        This medium size is $17.99. Delivery is $2.50 more, bringing your total $20.49!
    */
    

Aktifkan konteks yang dapat dibatalkan

Sekarang Anda akan mengaktifkan konteks nullable dan mengevaluasi efeknya pada build.

  1. Di src/ContosoPizza.Service/ContosoPizza.Service.csproj, tambahkan baris yang disorot dan simpan perubahan Anda:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    
      <ItemGroup>
        <ProjectReference Include="..\ContosoPizza.Models\ContosoPizza.Models.csproj" />
      </ItemGroup>
    
    </Project>
    

    Perubahan sebelumnya mengaktifkan konteks nullable untuk seluruh proyek ContosoPizza.Service.

  2. Di src/ContosoPizza.Models/ContosoPizza.Models.csproj, tambahkan baris yang disorot dan simpan perubahan Anda:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    
    </Project>
    

    Perubahan sebelumnya mengaktifkan konteks nullable untuk seluruh proyek ContosoPizza.Models.

  3. Bangun solusi sampel menggunakan perintah dotnet build.

    dotnet build
    

    Build berhasil dengan 2 peringatan.

    dotnet build
    Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
    Copyright (C) Microsoft Corporation. All rights reserved.
    
      Determining projects to restore...
      Restored .\src\ContosoPizza.Service\ContosoPizza.Service.csproj (in 477 ms).
      Restored .\src\ContosoPizza.Models\ContosoPizza.Models.csproj (in 475 ms).
    .\src\ContosoPizza.Models\Pizza.cs(3,28): warning CS8618: Non-nullable property 'Cheeses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
    .\src\ContosoPizza.Models\Pizza.cs(3,28): warning CS8618: Non-nullable property 'Toppings' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
      ContosoPizza.Models -> .\src\ContosoPizza.Models\bin\Debug\net6.0\ContosoPizza.Models.dll
      ContosoPizza.Service -> .\src\ContosoPizza.Service\bin\Debug\net6.0\ContosoPizza.Service.dll
    
    Build succeeded.
    
    .\src\ContosoPizza.Models\Pizza.cs(3,28): warning CS8618: Non-nullable property 'Cheeses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
    .\src\ContosoPizza.Models\Pizza.cs(3,28): warning CS8618: Non-nullable property 'Toppings' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
        2 Warning(s)
        0 Error(s)
    
    Time Elapsed 00:00:07.48
    
  4. Bangun solusi sampel lagi menggunakan perintah dotnet build.

    dotnet build
    

    Kali ini, build berhasil tanpa kesalahan atau peringatan. Build sebelumnya berhasil dalam penyelesaian, namun dengan peringatan. Karena sumber tidak berubah, proses pembuatan tidak menjalankan kompiler lagi. Karena build tidak menjalankan kompiler, tidak ada peringatan.

    Tip

    Anda dapat memaksa membangun kembali semua rakitan dalam proyek dengan menggunakan perintah dotnet clean sebelum dotnet build.

  5. Dalam file .csproj, tambahkan baris yang disorot dan simpan perubahan Anda.

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
      </PropertyGroup>
    
      <ItemGroup>
        <ProjectReference Include="..\ContosoPizza.Models\ContosoPizza.Models.csproj" />
      </ItemGroup>
    
    </Project>
    
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
      </PropertyGroup>
    
    </Project>
    

    Perubahan sebelumnya menginstruksikan kompiler untuk gagal membangun setiap kali ada peringatan.

    Tip

    Penggunaan <TreatWarningsAsErrors> bersifat opsional. Namun, kami merekomendasikannya karena memastikan Anda tidak mengabaikan peringatan apa pun.

  6. Bangun solusi sampel menggunakan perintah dotnet build.

    dotnet build
    

    Build gagal dengan 2 kesalahan.

    dotnet build
    Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
    Copyright (C) Microsoft Corporation. All rights reserved.
    
      Determining projects to restore...
      All projects are up-to-date for restore.
    .\src\ContosoPizza.Models\Pizza.cs(3,28): error CS8618: Non-nullable property 'Cheeses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
    .\src\ContosoPizza.Models\Pizza.cs(3,28): error CS8618: Non-nullable property 'Toppings' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
    
    Build FAILED.
    
    .\src\ContosoPizza.Models\Pizza.cs(3,28): error CS8618: Non-nullable property 'Cheeses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
    .\src\ContosoPizza.Models\Pizza.cs(3,28): error CS8618: Non-nullable property 'Toppings' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [.\src\ContosoPizza.Models\ContosoPizza.Models.csproj]
        0 Warning(s)
        2 Error(s)
    
    Time Elapsed 00:00:02.95
    

    Saat memperlakukan peringatan sebagai kesalahan, aplikasi tidak lagi dapat dibangun. Ini sebenarnya diinginkan dalam situasi ini, karena jumlah kesalahannya kecil dan kami akan segera mengatasinya. Dua kesalahan (CS8618) memberi tahu bahwa ada properti yang dinyatakan tidak bisa bernilai null namun belum diinisialisasi.

Perbaiki kesalahan

Ada banyak taktik untuk mengatasi peringatan/kesalahan yang terkait dengan nullability. Beberapa contohnya termasuk:

  • Memerlukan koleksi keju dan topping yang tidak dapat diubah ke null sebagai parameter konstruktor
  • Mengambil alih properti get/set dan menambahkan pengecekan null
  • Mengekspresikan niat agar properti dapat memiliki nilai null
  • Menginisialisasi koleksi dengan nilai default (kosong) secara langsung menggunakan inisialisasi properti.
  • Menetapkan nilai properti default (kosong) di konstruktor
  1. Untuk memperbaiki kesalahan pada properti Pizza.Cheeses, ubah definisi properti pada Pizza.cs untuk menambahkan tanda centang null. Ini tidak benar-benar pizza tanpa keju, bukan?

    namespace ContosoPizza.Models;
    
    public sealed record class Pizza([Required] string Name)
    {
        private ICollection<PizzaCheese>? _cheeses;
    
        public int Id { get; set; }
    
        [Range(0, 9999.99)]
        public decimal Price { get; set; }
    
        public PizzaSize Size { get; set; }
        public PizzaCrust Crust { get; set; }
        public PizzaSauce Sauce { get; set; }
    
        public ICollection<PizzaCheese> Cheeses
        {
            get => (_cheeses ??= new List<PizzaCheese>());
            set => _cheeses = value ?? throw new ArgumentNullException(nameof(value));
        }
    
        public ICollection<PizzaTopping>? Toppings { get; set; }
    
        public override string ToString() => this.ToDescriptiveString();
    }
    

    Dalam kode sebelumnya:

    • Bidang pendukung baru ditambahkan untuk membantu mencegat pengakses properti get dan set bernama _cheeses. Itu dinyatakan sebagai nullable (?) dan dibiarkan tanpa inisialisasi.
    • Pengakses get dipetakan ke ekspresi yang menggunakan operator penggabungan-nol (??). Ekspresi ini mengembalikan bidang _cheeses, dengan asumsi itu bukan null. Jika null, hal tersebut menetapkan _cheeses ke new List<PizzaCheese>() sebelum mengembalikan _cheeses.
    • Pengakses set juga dipetakan menjadi ekspresi dan menggunakan operator penggabungan-nol. Saat konsumen menetapkan nilai null, maka ArgumentNullException dibuang.
  2. Karena tidak semua pizza memiliki topping, null mungkin merupakan nilai yang valid untuk properti Pizza.Toppings. Dalam hal ini, masuk akal untuk menyatakannya sebagai nullable.

    1. Ubah definisi properti di Pizza.cs agar Toppings dapat dibatalkan.

      namespace ContosoPizza.Models;
      
      public sealed record class Pizza([Required] string Name)
      {
          private ICollection<PizzaCheese>? _cheeses;
      
          public int Id { get; set; }
      
          [Range(0, 9999.99)]
          public decimal Price { get; set; }
      
          public PizzaSize Size { get; set; }
          public PizzaCrust Crust { get; set; }
          public PizzaSauce Sauce { get; set; }
      
          public ICollection<PizzaCheese> Cheeses
          {
              get => (_cheeses ??= new List<PizzaCheese>());
              set => _cheeses = value ?? throw new ArgumentNullException(nameof(value));
          }
      
          public ICollection<PizzaTopping>? Toppings { get; set; }
      
          public override string ToString() => this.ToDescriptiveString();
      }
      

      Properti Toppings sekarang dinyatakan dapat bernilai null.

    2. Tambahkan baris yang disorot ke ContosoPizza.Service\Program.cs:

      using ContosoPizza.Models;
      
      // Create a pizza
      Pizza pizza = new("Meat Lover's Special")
      {
          Size = PizzaSize.Medium,
          Crust = PizzaCrust.DeepDish,
          Sauce = PizzaSauce.Marinara,
          Price = 17.99m,
      };
      
      // Add cheeses
      pizza.Cheeses.Add(PizzaCheese.Mozzarella);
      pizza.Cheeses.Add(PizzaCheese.Parmesan);
      
      // Add toppings
      pizza.Toppings ??= new List<PizzaTopping>();
      pizza.Toppings.Add(PizzaTopping.Sausage);
      pizza.Toppings.Add(PizzaTopping.Pepperoni);
      pizza.Toppings.Add(PizzaTopping.Bacon);
      pizza.Toppings.Add(PizzaTopping.Ham);
      pizza.Toppings.Add(PizzaTopping.Meatballs);
      
      Console.WriteLine(pizza);
      
      /*
          Expected output:
      
          The "Meat Lover's Special" is a deep dish pizza with marinara sauce.
          It's covered with a blend of mozzarella and parmesan cheese.
          It's layered with sausage, pepperoni, bacon, ham and meatballs.
          This medium size is $17.99. Delivery is $2.50 more, bringing your total $20.49!
      */
      

    Dalam kode sebelumnya, operator nol-koalesensi digunakan untuk menetapkan Toppings ke new List<PizzaTopping>(); jika new List<PizzaTopping>(); adalah .

Jalankan solusi yang sudah selesai

  1. Simpan semua perubahan Anda dan kemudian bangun solusinya.

    dotnet build
    

    Build selesai tanpa peringatan atau kesalahan.

  2. Jalankan aplikasi.

    dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
    

    Aplikasi berjalan hingga selesai (tanpa kesalahan) dan menampilkan output berikut:

    dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
    The "Meat Lover's Special" is a deep dish pizza with marinara sauce.
    It's covered with a blend of mozzarella and parmesan cheese.
    It's layered with sausage, pepperoni, bacon, ham and meatballs.
    This medium size is $17.99. Delivery is $2.50 more, bringing your total $20.49!
    

Ringkasan

Di unit ini, Anda menggunakan konteks nullable untuk mengidentifikasi dan mencegah kemungkinan NullReferenceException kemunculan dalam kode Anda.