Übung: Anwenden von Nullsicherheitsstrategien
In der vorherigen Lerneinheit haben Sie erfahren, wie Sie Ihre Absicht für die NULL-Zulässigkeit im Code ausdrücken. In dieser Lerneinheit wenden Sie das Gelernte auf ein vorhandenes C#-Projekt an.
Hinweis
In diesem Modul wird die .NET-CLI (Befehlszeilenschnittstelle) und Visual Studio Code für die lokale Entwicklung verwendet. Nach Abschluss dieses Moduls können Sie die Konzepte mit Visual Studio (Windows), Visual Studio für Mac (macOS) oder bei der Weiterentwicklung mit Visual Studio Code (Windows, Linux und macOS) anwenden.
In diesem Modul wird das .NET 6.0 SDK verwendet. Stellen Sie sicher, dass .NET 6.0 installiert ist, indem Sie in Ihrem bevorzugten Terminal den folgenden Befehl ausführen:
dotnet --list-sdks
Daraufhin wird eine Ausgabe angezeigt, die in etwa wie folgt aussieht:
3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]
Stellen Sie sicher, dass eine Version aufgeführt wird, die mit 6
beginnt. Wenn keine solche Version aufgeführt wird oder der Befehl nicht gefunden wurde, installieren Sie das neueste .NET 6.0 SDK.
Abrufen und Untersuchen des Beispielcodes
Klonen Sie in einem Befehlsterminal das GitHub-Beispielrepository, und wechseln Sie zum geklonten Verzeichnis.
git clone https://github.com/microsoftdocs/mslearn-csharp-null-safety cd mslearn-csharp-null-safety
Öffnen Sie das Projektverzeichnis in Visual Studio Code.
code .
Führen Sie das Beispielprojekt mit dem Befehl
dotnet run
aus.dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
Dies führt dazu, dass eine NullReferenceException ausgelöst wird.
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
Die Stapelüberwachung gibt an, dass die Ausnahme in Zeile 13 in .\src\ContosoPizza.Service\Program.cs aufgetreten ist. In Zeile 13 wird die
Add
-Methode für diepizza.Cheeses
-Eigenschaft aufgerufen. Dapizza.Cheeses
null
ist, wird eine NullReferenceException ausgelöst.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! */
Aktivieren des Nullable-Kontexts
Sie werden jetzt einen Nullwerte zulassenden Kontext aktivieren und dessen Auswirkungen auf den Build untersuchen.
Fügen Sie in src/ContosoPizza.Service/ContosoPizza.Service.csproj die hervorgehobene Zeile hinzu, und speichern Sie Ihre Änderungen:
<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>
Die vorherige Änderung aktiviert den Nullable-Kontext für das gesamte
ContosoPizza.Service
-Projekt.Fügen Sie in src/ContosoPizza.Models/ContosoPizza.Models.csproj die hervorgehobene Zeile hinzu, und speichern Sie Ihre Änderungen:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>
Die vorherige Änderung aktiviert den Nullable-Kontext für das gesamte
ContosoPizza.Models
-Projekt.Erstellen Sie die Beispielprojektmappe mit dem
dotnet build
-Befehl.dotnet build
Der Buildvorgang wird erfolgreich mit zwei Warnungen abgeschlossen.
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
Erstellen Sie die Beispielprojektmappe erneut mit dem
dotnet build
-Befehl.dotnet build
Dieses Mal wird der Build ohne Fehler oder Warnungen abgeschlossen. Der vorherige Build wurde erfolgreich mit Warnungen abgeschlossen. Da sich der Quellcode nicht geändert hat, führt der Buildprozess den Compiler nicht erneut aus. Da der Build den Compiler nicht ausführt, gibt es keine Warnungen.
Tipp
Sie können eine Neuerstellung aller Assemblys in einem Projekt erzwingen, indem Sie den
dotnet clean
-Befehl vordotnet build
verwenden.Fügen Sie in den CSPROJ-Dateien die hervorgehobenen Zeilen hinzu, und speichern Sie Ihre Änderungen.
<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>
Die vorherigen Änderungen weisen den Compiler an, den Build fehlschlagen zu lassen, wenn eine Warnung auftritt.
Tipp
Die Verwendung von
<TreatWarningsAsErrors>
ist optional. Wir empfehlen sie jedoch, da dadurch sichergestellt wird, dass Sie keine Warnungen übersehen.Erstellen Sie die Beispielprojektmappe mit dem
dotnet build
-Befehl.dotnet build
Der Build schlägt mit zwei Fehlern fehl.
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
Wenn Warnungen als Fehler behandelt werden, wird die App nicht mehr erstellt. Dies ist in dieser Situation tatsächlich erwünscht, da die Anzahl der Fehler klein ist und wir sie schnell beheben können. Die beiden Fehler (CS8618) informieren Sie darüber, dass Eigenschaften als Non-Nullable-Eigenschaften deklariert sind, die noch nicht initialisiert wurden.
Beheben der Fehler
Es gibt viele Taktiken, um die Warnungen/Fehler im Zusammenhang mit der NULL-Zulässigkeit zu beheben. Beispiele hierfür sind:
- Vorschreiben einer Non-Nullable-Sammlung von Käsesorten und Belägen als Konstruktorparametern.
- Abfangen der Eigenschaft
get
/set
und Hinzufügen einernull
-Überprüfung. - Ausdrücken der Absicht, dass die Eigenschaften Nullwerte zulassen sollen
- Initialisieren der Sammlung mit einem (leeren) Standardwert inline mithilfe von Eigenschafteninitialisierern
- Zuweisen eines (leeren) Standardwerts zur Eigenschaft im Konstruktor
Zum Beheben des Fehlers bei der
Pizza.Cheeses
-Eigenschaft ändern Sie die Eigenschaftsdefinition in Pizza.cs, um einenull
-Prüfung hinzuzufügen. Ohne Käse ist es ja eigentlich keine Pizza, oder?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(); }
Für den Code oben gilt:
- Ein neues Unterstützungsfeld mit dem Namen
_cheeses
wird hinzugefügt, um die Eigenschaftenaccessorenget
undset
abzufangen. Es wird als Nullwerte zulassend (?
) deklariert und bleibt uninitialisiert. - Der
get
-Accessor wird einem Ausdruck zugeordnet, der den Null-Sammeloperator (??
) verwendet. Dieser Ausdruck gibt das_cheeses
-Feld zurück, vorausgesetzt, es ist nichtnull
. Wenn esnull
ist, weist ernew List<PizzaCheese>()
_cheeses
zu, bevor er_cheeses
zurückgibt. - Der
set
-Accessor wird ebenfalls einem Ausdruck zugeordnet und verwendet den Null-Sammeloperator. Wenn ein Consumer einennull
-Wert zuweist, wird die ArgumentNullException ausgelöst.
- Ein neues Unterstützungsfeld mit dem Namen
Da nicht alle Pizzen Beläge aufweisen, kann
null
ein gültiger Wert für diePizza.Toppings
-Eigenschaft sein. In diesem Fall ist es sinnvoll, sie als Nullwerte zulassend zu formulieren.Ändern Sie die Eigenschaftendefinition in Pizza.cs so, dass Nullwerte für
Toppings
zulässig sind.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(); }
Die
Toppings
-Eigenschaft wird jetzt als Nullwerte zulassend angegeben.Fügen Sie ContosoPizza.Service\Program.cs die hervorgehobene Zeile hinzu:
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! */
Im vorangehenden Code wird der Null-Sammeloperator verwendet, um
new List<PizzaTopping>();
Toppings
zuzuweisen, wenn sienull
ist.
Ausführen der abgeschlossenen Projektmappe
Speichern Sie alle Änderungen, und erstellen Sie dann die Projektmappe.
dotnet build
Der Buildvorgang wird ohne Fehler oder Warnungen abgeschlossen.
Führen Sie die App aus.
dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
Die App wird bis zum Abschluss (ohne Fehler) ausgeführt und zeigt die folgende Ausgabe an:
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!
Zusammenfassung
In dieser Einheit haben Sie einen Nullable-Kontext verwendet, um mögliche Vorkommen von NullReferenceException
in Ihrem Code zu identifizieren und zu verhindern.