Ö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.csprojDetta 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 13Stackspårningen anger att undantaget inträffade på rad 13 i .\src\ContosoPizza.Service\Program.cs. På rad 13
Addanropas metoden förpizza.Cheesesegenskapen . Eftersompizza.Cheesesärnull, 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 buildBygget 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.48Skapa exempellösningen igen med kommandot
dotnet build.dotnet buildDen 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öredotnet 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 buildBygget 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.95Nä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 ennullkontroll. 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 egenskapenPizza.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
Toppingstillnew List<PizzaTopping>();om den ärnull.
Kör den slutförda lösningen
Spara alla dina ändringar och skapa sedan lösningen.
dotnet buildBygget slutförs utan varningar eller fel.
Kör appen.
dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csprojAppen 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.