Oefening: strategieën voor null-veiligheid toepassen
In de vorige les hebt u geleerd over het uitdrukken van uw intentie voor null-baarheid in code. In deze les past u toe wat u hebt geleerd op een bestaand C#-project.
Notitie
Deze module maakt gebruik van de .NET CLI (Opdrachtregelinterface) en Visual Studio Code voor lokale ontwikkeling. Nadat u deze module hebt voltooid, kunt u de concepten toepassen met behulp van Visual Studio (Windows), Visual Studio voor Mac (macOS) of continue ontwikkeling met behulp van Visual Studio Code (Windows, Linux en macOS).
In deze module wordt de .NET 6.0 SDK gebruikt. Zorg ervoor dat .NET 6.0 is geïnstalleerd door de volgende opdracht uit te voeren in de terminal van uw voorkeur:
dotnet --list-sdks
Uitvoer van de volgende strekking weergegeven:
3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]
Zorg ervoor dat er een versie wordt vermeld die begint met 6
. Als er geen wordt vermeld of de opdracht niet wordt gevonden, installeert u de meest recente .NET 6.0 SDK.
De voorbeeldcode ophalen en onderzoeken
Kloon in een opdrachtterminal de GitHub-voorbeeldopslagplaats en schakel over naar de gekloonde map.
git clone https://github.com/microsoftdocs/mslearn-csharp-null-safety cd mslearn-csharp-null-safety
Open de projectmap in Visual Studio Code.
code .
Voer het voorbeeldproject uit met behulp van de
dotnet run
opdracht.dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
Dit leidt tot een NullReferenceException gegooid.
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
De stacktracering geeft aan dat de uitzondering is opgetreden op regel 13 in .\src\ContosoPizza.Service\Program.cs. Op regel 13 wordt de
Add
methode aangeroepen op depizza.Cheeses
eigenschap. Omdatpizza.Cheeses
isnull
, wordt er een NullReferenceException gegooid.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! */
Null-context inschakelen
Nu schakelt u een null-context in en bekijkt u het effect ervan op de build.
Voeg in src/ContosoPizza.Service/ContosoPizza.Service.csproj de gemarkeerde regel toe en sla uw wijzigingen op:
<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>
Met de voorgaande wijziging wordt de null-context voor het hele
ContosoPizza.Service
project ingeschakeld.Voeg in src/ContosoPizza.Models/ContosoPizza.Models.csproj de gemarkeerde regel toe en sla uw wijzigingen op:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>
Met de voorgaande wijziging wordt de null-context voor het hele
ContosoPizza.Models
project ingeschakeld.Bouw de voorbeeldoplossing met behulp van de
dotnet build
opdracht.dotnet build
De build slaagt met 2 waarschuwingen.
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
Bouw de voorbeeldoplossing opnieuw met behulp van de
dotnet build
opdracht.dotnet build
Deze keer slaagt de build zonder fouten of waarschuwingen. De vorige build is voltooid, met waarschuwingen. Omdat de bron niet is gewijzigd, voert het buildproces de compiler niet opnieuw uit. Omdat de build de compiler niet uitvoert, zijn er geen waarschuwingen.
Tip
U kunt een herbouw van alle assembly's in een project afdwingen met behulp van de
dotnet clean
opdracht voorafgaand aandotnet build
.Voeg in de .csproj-bestanden de gemarkeerde regels toe en sla uw wijzigingen op.
<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 vorige wijzigingen geven aan dat de compiler de build mislukt wanneer er een waarschuwing wordt aangetroffen.
Tip
Het gebruik van
<TreatWarningsAsErrors>
is optioneel. Het wordt echter aangeraden om ervoor te zorgen dat u geen waarschuwingen over het hoofd ziet.Bouw de voorbeeldoplossing met behulp van de
dotnet build
opdracht.dotnet build
De build mislukt met 2 fouten.
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
Bij het behandelen van waarschuwingen als fouten, wordt de app niet meer gebouwd. Dit is in feite gewenst in deze situatie, omdat het aantal fouten klein is en we zullen ze snel aanpakken. De twee fouten (CS8618) laten u weten dat er eigenschappen zijn gedeclareerd als niet-nullable die nog niet zijn geïnitialiseerd.
De fouten oplossen
Er zijn veel tactieken om de waarschuwingen/fouten met betrekking tot null-baarheid op te lossen. Enkele voorbeelden:
- Een niet-nullable verzameling kaas en toppings vereisen als constructorparameters
- De eigenschap
get
/set
onderscheppen en eennull
controle toevoegen - De intentie uitdrukken voor de eigenschappen die null kunnen worden
- Initialiseer de verzameling met een standaardwaarde (lege) inline met behulp van initialisatieprogramma's van eigenschappen
- De eigenschap een standaardwaarde (leeg) toewijzen in de constructor
Als u de fout in de
Pizza.Cheeses
eigenschap wilt oplossen, wijzigt u de eigenschapsdefinitie op Pizza.cs om eennull
controle toe te voegen. Het is niet echt een pizza zonder kaas, hè?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(); }
In de voorgaande code:
- Er wordt een nieuw backingveld toegevoegd om de toegangsrechten voor eigenschappen te onderscheppen met
set
deget
naam_cheeses
. Deze wordt gedeclareerd als nullable (?
) en niet-geïnitialiseerd. - De
get
accessor wordt toegewezen aan een expressie die gebruikmaakt van de operator null-coalescing (??
). Deze expressie retourneert het_cheeses
veld, ervan uitgaande dat dit nietnull
het is. Als dit het isnull
, wordt het toegewezennew List<PizzaCheese>()
aan voordat u terugkeert_cheeses
_cheeses
. - De
set
accessor wordt ook toegewezen aan een expressie en maakt gebruik van de operator null-coalescing. Wanneer een consument eennull
waarde toewijst, wordt deze ArgumentNullException gegenereerd.
- Er wordt een nieuw backingveld toegevoegd om de toegangsrechten voor eigenschappen te onderscheppen met
Omdat niet alle pizza's toppings hebben,
null
kan dit een geldige waarde voor dePizza.Toppings
eigenschap zijn. In dit geval is het zinvol om het uit te drukken als nullable.Wijzig de eigenschapsdefinitie op Pizza.cs zodat deze
Toppings
nullable kan zijn.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(); }
De
Toppings
eigenschap wordt nu uitgedrukt als nullable.Voeg de gemarkeerde regel toe aan 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! */
In de voorgaande code wordt de operator null-coalescing gebruikt om toe te wijzen
Toppings
new List<PizzaTopping>();
als dit het geval isnull
.
De voltooide oplossing uitvoeren
Sla al uw wijzigingen op en bouw vervolgens de oplossing.
dotnet build
De build wordt zonder waarschuwingen of fouten voltooid.
De app uitvoeren.
dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
De app wordt uitgevoerd tot voltooiing (zonder fout) en geeft de volgende uitvoer weer:
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!
Samenvatting
In deze les hebt u een null-context gebruikt om mogelijke NullReferenceException
gebeurtenissen in uw code te identificeren en te voorkomen.