Ö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
Add
anropas metoden förpizza.Cheeses
egenskapen . 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.Service
projektet.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.Models
projektet.Skapa exempellösningen
dotnet build
med 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 clean
av 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 build
med 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
/set
och lägg till ennull
kontroll - 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.Cheeses
egenskapen genom att ändra egenskapsdefinitionen på Pizza.cs för att lägga till ennull
kontroll. 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
get
ochset
med namnet_cheeses
. Den deklareras som nullbar (?
) och lämnas oinitierad. - Accessorn
get
mappas 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 ärnull
tilldelar_cheeses
den tillnew List<PizzaCheese>()
innan den returnerar_cheeses
. - Accessorn
set
mappas också till ett uttryck och använder operatorn null-coalescing. När en konsument tilldelar ettnull
vä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
null
vara 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
Toppings
vara 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
Toppings
uttrycks 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
Toppings
tillnew List<PizzaTopping>();
om den ärnull
.
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.