Övning – Tillämpa null-säkerhetsstrategier
I föregående lektion lärde du dig att uttrycka avsikten med nullabilitet i kod. I den här lektionen använder du det du har lärt dig för ett befintligt C#-projekt.
Kommentar
Den här modulen använder .NET CLI (Kommandoradsgränssnitt) och Visual Studio Code för lokal utveckling. När du har slutfört den här modulen kan du använda begreppen med Hjälp av Visual Studio (Windows), Visual Studio för Mac (macOS) eller fortsatt utveckling med Hjälp av Visual Studio Code (Windows, Linux och macOS).
Den här modulen använder .NET 6.0 SDK. Kontrollera att du har .NET 6.0 installerat genom att köra följande kommando i önskad terminal:
dotnet --list-sdks
Utdata som liknar följande visas:
3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]
Kontrollera att en version som börjar med 6 visas. Om inget visas eller om kommandot inte hittas installerar du den senaste .NET 6.0 SDK:t.
Hämta och granska exempelkoden
- I en kommandoterminal klonar du GitHub-exempellagringsplatsen och växlar till den klonade katalogen. - git clone https://github.com/microsoftdocs/mslearn-csharp-null-safety cd mslearn-csharp-null-safety
- Öppna projektkatalogen i Visual Studio Code. - code .
- Kör exempelprojektet med kommandot - dotnet run.- dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj- Detta resulterar i att en NullReferenceException utlöses. - 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- Stackspårningen anger att undantaget inträffade på rad 13 i .\src\ContosoPizza.Service\Program.cs. På rad 13 - Addanropas metoden för- pizza.Cheesesegenskapen . Eftersom- pizza.Cheesesär- null, kastas en NullReferenceException .- 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! */
Aktivera null-kontext
Nu ska du aktivera en nullbar kontext och undersöka dess effekt på bygget.
- I src/ContosoPizza.Service/ContosoPizza.Service.csproj lägger du till den markerade raden och sparar ändringarna: - <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>- Föregående ändring aktiverar den nullbara kontexten för hela - ContosoPizza.Serviceprojektet.
- I src/ContosoPizza.Models/ContosoPizza.Models.csproj lägger du till den markerade raden och sparar ändringarna: - <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>- Föregående ändring aktiverar den nullbara kontexten för hela - ContosoPizza.Modelsprojektet.
- Skapa exempellösningen - dotnet buildmed kommandot .- dotnet build- Bygget lyckas med två varningar. - 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
- Skapa exempellösningen igen med kommandot - dotnet build.- dotnet build- Den här gången lyckas bygget utan fel eller varningar. Den tidigare versionen slutfördes med varningar. Eftersom källan inte ändrades kör byggprocessen inte kompilatorn igen. Eftersom kompilatorn inte körs finns det inga varningar. - Dricks - Du kan tvinga fram en ombyggnad av alla sammansättningar i ett projekt med hjälp - dotnet cleanav kommandot före- dotnet build.
- I .csproj-filerna lägger du till de markerade raderna och sparar ändringarna. - <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>- De tidigare ändringarna instruerar kompilatorn att misslyckas med bygget när en varning påträffas. - Dricks - Det är valfritt att använda - <TreatWarningsAsErrors>. Vi rekommenderar det dock eftersom det säkerställer att du inte förbiser några varningar.
- Skapa exempellösningen - dotnet buildmed kommandot .- dotnet build- Bygget misslyckas med 2 fel. - 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- När du behandlar varningar som fel skapas inte längre appen. Detta är faktiskt önskvärt i den här situationen, eftersom antalet fel är litet och vi kommer snabbt att åtgärda dem. De två felen (CS8618) låter dig veta att det finns egenskaper som deklarerats som icke-nullbara som ännu inte har initierats. 
Åtgärda felen
Det finns många metoder för att lösa varningar/fel som rör nullbarhet. Vissa exempel inkluderar:
- Kräv en icke-nullbar samling ostar och pålägg som konstruktorparametrar
- Fånga upp egenskapen get/setoch lägg till ennullkontroll
- Uttrycka avsikten att egenskaperna ska vara nullbara
- Initiera samlingen med ett standardvärde (tomt) infogat med egenskapsinitierare
- Tilldela egenskapen ett standardvärde (tomt) i konstruktorn
- Åtgärda felet på - Pizza.Cheesesegenskapen genom att ändra egenskapsdefinitionen på Pizza.cs för att lägga till en- nullkontroll. Det är inte riktigt en pizza utan ost, eller hur?- 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(); }- I koden ovan: - Ett nytt bakgrundsfält läggs till för att hjälpa till att fånga upp egenskapsåtkomsterna getochsetmed namnet_cheeses. Den deklareras som nullbar (?) och lämnas oinitierad.
- Accessorn getmappas till ett uttryck som använder null-coalescing-operatorn (??). Det här uttrycket returnerar fältet_cheeses, förutsatt att det intenullär . Om den ärnulltilldelar_cheesesden tillnew List<PizzaCheese>()innan den returnerar_cheeses.
- Accessorn setmappas också till ett uttryck och använder operatorn null-coalescing. När en konsument tilldelar ettnullvärde ArgumentNullException genereras.
 
- Ett nytt bakgrundsfält läggs till för att hjälpa till att fånga upp egenskapsåtkomsterna 
- Eftersom inte alla pizzor har toppings kan - nullvara ett giltigt värde för egenskapen- Pizza.Toppings. I det här fallet är det klokt att uttrycka det som nullbart.- Ändra egenskapsdefinitionen på Pizza.cs så att den kan - Toppingsvara 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(); }- Egenskapen - Toppingsuttrycks nu som nullbar.
- Lägg till den markerade raden i 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! */
 - I föregående kod används operatorn null-coalescing för att tilldela - Toppingstill- new List<PizzaTopping>();om den är- null.
Kör den slutförda lösningen
- Spara alla dina ändringar och skapa sedan lösningen. - dotnet build- Bygget slutförs utan varningar eller fel. 
- Kör appen. - dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj- Appen körs till slutförande (utan fel) och visar följande utdata: - 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!
Sammanfattning
I den här lektionen använde du en nullbar kontext för att identifiera och förhindra möjliga NullReferenceException förekomster i koden.