Cvičení – použití strategií zabezpečení s hodnotou null
V předchozí lekci jste se dozvěděli o vyjádření záměru nullability v kódu. V této lekci použijete to, co jste se naučili v existujícím projektu C#.
Poznámka:
Tento modul používá rozhraní příkazového řádku .NET (rozhraní příkazového řádku) a Visual Studio Code pro místní vývoj. Po dokončení tohoto modulu můžete použít koncepty pomocí sady Visual Studio (Windows), Visual Studio pro Mac (macOS) nebo průběžného vývoje pomocí editoru Visual Studio Code (Windows, Linux a macOS).
Tento modul používá sadu .NET 6.0 SDK. Spuštěním následujícího příkazu v upřednostňovaném terminálu se ujistěte, že máte nainstalovaný .NET 6.0:
dotnet --list-sdks
Zobrazí se výstup podobný následujícímu:
3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]
Ujistěte se, že je uvedená verze, na 6
které začíná. Pokud žádný není uvedený nebo příkaz nebyl nalezen, nainstalujte nejnovější sadu .NET 6.0 SDK.
Načtení a prozkoumání vzorového kódu
V příkazovém terminálu naklonujte ukázkové úložiště GitHub a přepněte do klonovaného adresáře.
git clone https://github.com/microsoftdocs/mslearn-csharp-null-safety cd mslearn-csharp-null-safety
Otevřete adresář projektu v editoru Visual Studio Code.
code .
Spusťte ukázkový projekt pomocí
dotnet run
příkazu.dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
Výsledkem bude NullReferenceException vyvolání.
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
Trasování zásobníku označuje, že došlo k výjimce na řádku 13 v souboru .\src\ContosoPizza.Service\Program.cs. Na řádku 13 je
Add
metoda volána vepizza.Cheeses
vlastnosti. Protožepizza.Cheeses
je , NullReferenceException jenull
vyvolán.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! */
Povolení kontextu s možnou hodnotou null
Teď povolíte kontext s možnou hodnotou null a prozkoumáte jeho účinek na sestavení.
V souboru src/ContosoPizza.Service/ContosoPizza.Service.csproj přidejte zvýrazněný řádek a uložte změny:
<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>
Předchozí změna umožňuje kontext s možnou hodnotou null pro celý
ContosoPizza.Service
projekt.V souboru src/ContosoPizza.Models/ContosoPizza.Models.csproj přidejte zvýrazněný řádek a uložte změny:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>
Předchozí změna umožňuje kontext s možnou hodnotou null pro celý
ContosoPizza.Models
projekt.Pomocí příkazu sestavte ukázkové řešení
dotnet build
.dotnet build
Sestavení proběhne úspěšně s 2 upozorněními.
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
Znovu sestavte ukázkové řešení pomocí
dotnet build
příkazu.dotnet build
Tentokrát bude sestavení úspěšné bez chyb nebo upozornění. Předchozí sestavení bylo úspěšně dokončeno s upozorněními. Vzhledem k tomu, že se zdroj nezměnil, proces sestavení znovu nespustí kompilátor. Vzhledem k tomu, že sestavení nespustí kompilátor, neexistují žádná upozornění.
Tip
Opětovné sestavení všech sestavení v projektu můžete vynutit pomocí
dotnet clean
příkazu předdotnet build
.V souborech .csproj přidejte zvýrazněné řádky a uložte změny.
<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>
Předchozí změny instruují kompilátoru, aby sestavení selhalo při každém zobrazení upozornění.
Tip
Použití
<TreatWarningsAsErrors>
je volitelné. Doporučujeme ho ale proto, abyste nepřehlédli žádná upozornění.Pomocí příkazu sestavte ukázkové řešení
dotnet build
.dotnet build
Sestavení selže s chybami 2.
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
Při zpracování upozornění jako chyb už aplikace nevytvoří sestavení. To je ve skutečnosti žádoucí v této situaci, protože počet chyb je malý a my je rychle vyřešíme. Dvě chyby (CS8618) umožňují zjistit, že jsou vlastnosti deklarované jako nenulové, které ještě nebyly inicializovány.
Oprava chyb
Existuje mnoho taktik k vyřešení upozornění nebo chyb souvisejících s nulovostí. Mezi některé příklady patří:
- Vyžadovat nenulovou kolekci sýrů a zastavování jako parametry konstruktoru
- Zachycení vlastnosti
get
/set
a přidání kontrolynull
- Vyjádření záměru pro vlastnosti, které mají být nullable
- Inicializace kolekce s výchozí (prázdnou) hodnotou vloženou pomocí inicializátorů vlastností
- Přiřaďte vlastnost výchozí (prázdnou) hodnotu v konstruktoru.
Chcete-li opravit chybu vlastnosti
Pizza.Cheeses
, upravte definici vlastnosti v Pizza.cs přidatnull
kontrolu. Není to opravdu pizza bez sýra, že?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(); }
V předchozím kódu:
- Přidá se nové pomocné pole, které pomáhá zachytit
get
přístupové objekty aset
přístupové objekty vlastností s názvem_cheeses
. Deklaruje se jako nullable (?
) a ponechá neinicializovaný. - Přístupový
get
objekt je namapován na výraz, který používá operátor nulového sjednocení (??
). Tento výraz vrátí pole za předpokladu_cheeses
, že nenínull
. Pokud jenull
, přiřadí_cheeses
senew List<PizzaCheese>()
před vrácením_cheeses
. - Přístupový
set
objekt je také mapován na výraz a používá operátor nulového sjednocení. Když příjemce přiřadínull
hodnotu, je ArgumentNullException vyvolán.
- Přidá se nové pomocné pole, které pomáhá zachytit
Vzhledem k tomu, že ne všechny pizzy mají zastavování,
null
může být platná hodnota proPizza.Toppings
vlastnost. V tomto případě je vhodné ho vyjádřit jako možnou hodnotu null.Upravte definici vlastnosti v Pizza.cs tak, aby umožňovala
Toppings
hodnotu null.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(); }
Vlastnost
Toppings
je nyní vyjádřena jako nullable.Přidejte zvýrazněný řádek do 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! */
V předchozím kódu se operátor null-coalescing používá k přiřazení
Toppings
,new List<PizzaTopping>();
pokud jenull
.
Spuštění dokončeného řešení
Uložte všechny provedené změny a pak sestavte řešení.
dotnet build
Sestavení se dokončí bez upozornění nebo chyb.
Spustit aplikaci.
dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
Aplikace se spustí na dokončení (bez chyby) a zobrazí následující výstup:
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!
Shrnutí
V této lekci jste použili kontext s možnou hodnotou null k identifikaci a zabránění možným NullReferenceException
výskytům v kódu.