Latihan - Menerapkan strategi keamanan null
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
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-safetyBuka direktori proyek di Visual Studio Code.
code .Jalankan proyek contoh menggunakan perintah
dotnet run.dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csprojIni 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 13Jejak tumpukan menunjukkan bahwa pengecualian terjadi pada baris 13 di .\src\ContosoPizza.Service\Program.cs. Pada baris 13, metode
Adddipanggil pada propertipizza.Cheeses. Karenapizza.Cheesesadalahnull, 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.
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.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.Bangun solusi sampel menggunakan perintah
dotnet build.dotnet buildBuild 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.48Bangun solusi sampel lagi menggunakan perintah
dotnet build.dotnet buildKali 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 cleansebelumdotnet build.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.Bangun solusi sampel menggunakan perintah
dotnet build.dotnet buildBuild 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.95Saat 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/setdan menambahkan pengecekannull - 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
Untuk memperbaiki kesalahan pada properti
Pizza.Cheeses, ubah definisi properti pada Pizza.cs untuk menambahkan tanda centangnull. 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
getdansetbernama_cheeses. Itu dinyatakan sebagai nullable (?) dan dibiarkan tanpa inisialisasi. - Pengakses
getdipetakan ke ekspresi yang menggunakan operator penggabungan-nol (??). Ekspresi ini mengembalikan bidang_cheeses, dengan asumsi itu bukannull. Jikanull, hal tersebut menetapkan_cheeseskenew List<PizzaCheese>()sebelum mengembalikan_cheeses. - Pengakses
setjuga dipetakan menjadi ekspresi dan menggunakan operator penggabungan-nol. Saat konsumen menetapkan nilainull, maka ArgumentNullException dibuang.
- Bidang pendukung baru ditambahkan untuk membantu mencegat pengakses properti
Karena tidak semua pizza memiliki topping,
nullmungkin merupakan nilai yang valid untuk propertiPizza.Toppings. Dalam hal ini, masuk akal untuk menyatakannya sebagai nullable.Ubah definisi properti di Pizza.cs agar
Toppingsdapat 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
Toppingssekarang dinyatakan dapat bernilai null.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
Toppingskenew List<PizzaTopping>();jikanew List<PizzaTopping>();adalah .
Jalankan solusi yang sudah selesai
Simpan semua perubahan Anda dan kemudian bangun solusinya.
dotnet buildBuild selesai tanpa peringatan atau kesalahan.
Jalankan aplikasi.
dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csprojAplikasi 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.