Gyakorlat – Null biztonsági stratégiák alkalmazása

Befejeződött

Az előző leckében megismerkedett a nullability szándék kódban való kifejezésével. Ebben a leckében a tanultakat egy meglévő C#-projektre fogja alkalmazni.

Feljegyzés

Ez a modul a .NET CLI-t (parancssori felületet) és a Visual Studio Code-ot használja a helyi fejlesztéshez. A modul elvégzése után alkalmazhatja a fogalmakat a Visual Studio (Windows), a Mac Visual Studio (macOS) vagy a Visual Studio Code (Windows, Linux és macOS) használatával történő folyamatos fejlesztéssel.

Ez a modul a .NET 6.0 SDK-t használja. Győződjön meg arról, hogy telepítve van a .NET 6.0, ha az alábbi parancsot futtatja az előnyben részesített terminálban:

dotnet --list-sdks

A következőhöz hasonló kimenet jelenik meg:

3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]

Győződjön meg arról, hogy az első 6 verzió szerepel a listában. Ha egyik sem szerepel a listában, vagy a parancs nem található, telepítse a legújabb .NET 6.0 SDK-t.

A mintakód lekérése és vizsgálata

  1. Egy parancsterminálban klónozza a gitHub-mintaadattárat, és váltson a klónozott könyvtárra.

    git clone https://github.com/microsoftdocs/mslearn-csharp-null-safety
    cd mslearn-csharp-null-safety
    
  2. Nyissa meg a projektkönyvtárat a Visual Studio Code-ban.

    code .
    
  3. Futtassa a mintaprojektet a dotnet run paranccsal.

    dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
    

    Ez egy dobáshoz vezet NullReferenceException .

    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
    

    A verem nyomkövetése azt jelzi, hogy a kivétel a 13. sorban történt az .\src\ContosoPizza.Service\Program.cs. A 13. sorban a Add metódus meghívása a pizza.Cheeses tulajdonságon történik. Mivel pizza.Cheeses van null, a NullReferenceException dobás.

    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 értékű környezet engedélyezése

Most engedélyezi a null értékű környezeteket, és megvizsgálja annak a buildre gyakorolt hatását.

  1. Az src/ContosoPizza.Service/ContosoPizza.Service.csproj fájlban adja hozzá a kiemelt sort, és mentse a módosításokat:

    <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>
    

    Az előző módosítás lehetővé teszi a teljes ContosoPizza.Service projekt null értékű környezetét.

  2. Az src/ContosoPizza.Models/ContosoPizza.Models.csproj fájlban adja hozzá a kiemelt sort, és mentse a módosításokat:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    
    </Project>
    

    Az előző módosítás lehetővé teszi a teljes ContosoPizza.Models projekt null értékű környezetét.

  3. Hozza létre a mintamegoldást a dotnet build parancs használatával.

    dotnet build
    

    A build 2 figyelmeztetéssel sikeres.

    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
    
  4. Hozza létre újra a mintamegoldást a dotnet build paranccsal.

    dotnet build
    

    A build ezúttal hibák és figyelmeztetések nélkül sikeres lesz. Az előző build sikeresen befejeződött, figyelmeztetésekkel. Mivel a forrás nem változott, a buildelési folyamat nem futtatja újra a fordítót. Mivel a build nem futtatja a fordítót, nincsenek figyelmeztetések.

    Tipp.

    A projekt összes szerelvényének újraépítését az dotnet clean előző dotnet buildparancs használatával kényszerítheti.

  5. A .csproj fájlokban adja hozzá a kiemelt sorokat, és mentse a módosításokat.

    <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>
    

    Az előző módosítások arra utasítják a fordítót, hogy minden figyelmeztetés esetén meghiúsuljon a buildelés.

    Tipp.

    A használat <TreatWarningsAsErrors> nem kötelező. Javasoljuk azonban, hogy ne hagyja figyelmen kívül a figyelmeztetéseket.

  6. Hozza létre a mintamegoldást a dotnet build parancs használatával.

    dotnet build
    

    A build 2 hibával meghiúsul.

    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
    

    A figyelmeztetések hibákként való kezelésekor az alkalmazás már nem épül fel. Ebben a helyzetben ez valóban kívánatos, mivel a hibák száma kicsi, és gyorsan megoldjuk őket. A két hiba (CS8618) tudatja Önnel, hogy vannak olyan tulajdonságok, amelyek még nem inicializálva vannak, nem null értékűként vannak deklarálva.

A hibák elhárítása

A nulllással kapcsolatos figyelmeztetések/hibák elhárítására számos taktikát lehet találni. Néhány példa:

  • Nem null értékű sajt- és öntetgyűjtemény megkövetelése konstruktorparaméterként
  • A tulajdonság get/set elfogása és ellenőrzés null hozzáadása
  • A tulajdonságok null értékűvé nyilvánítási szándékának kifejezése
  • A gyűjtemény inicializálása alapértelmezett (üres) értékkel a beágyazott tulajdonság inicializálóival
  • A tulajdonság hozzárendelése alapértelmezett (üres) értékhez a konstruktorban
  1. A tulajdonság hibáinak Pizza.Cheeses kijavításához módosítsa a tulajdonságdefiníciót a Pizza.cs egy ellenőrzés hozzáadásához null . Ez nem igazán pizza sajt nélkül, ugye?

    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();
    }
    

    A fenti kód a következőket végzi el:

    • A rendszer új háttérmezőt ad hozzá, amely segít elfogni a névvel ellátott _cheesesés set a get tulajdonsághoz tartozó tartozékokat. Null értékűnek (?) van deklarálva, és nem inicializálva marad.
    • A get tartozék egy null-szenesítő operátort (??) használó kifejezésre van leképezve. Ez a kifejezés a _cheeses mezőt adja vissza, feltéve, hogy nem null. Ha így van null, a visszatérés előtt hozzárendeli new List<PizzaCheese>() _cheeses _cheeses.
    • A set tartozék egy kifejezésre is le van képezve, és a null-szenesítő operátort használja. Amikor egy fogyasztó hozzárendel egy null értéket, a ArgumentNullException kidobás történik.
  2. Mivel nem minden pizza van öntet, lehet, null hogy érvényes érték a Pizza.Toppings tulajdonság. Ebben az esetben érdemes null értékűként kifejezni.

    1. Módosítsa a tulajdonságdefiníciót a Pizza.cs , hogy Toppings null értékű legyen.

      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();
      }
      

      A Toppings tulajdonság mostantól null értékűként van kifejezve.

    2. Adja hozzá a kiemelt sort a 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!
      */
      

    Az előző kódban a null-szenesítő operátort használjuk annak hozzárendeléséhez Toppings new List<PizzaTopping>(); , ha az null.

A kész megoldás futtatása

  1. Mentse az összes módosítást, majd hozza létre a megoldást.

    dotnet build
    

    A build figyelmeztetések és hibák nélkül befejeződik.

  2. Futtassa az alkalmazást.

    dotnet run --project src/ContosoPizza.Service/ContosoPizza.Service.csproj
    

    Az alkalmazás a befejezésig (hiba nélkül) fut, és a következő kimenetet jeleníti meg:

    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!
    

Összegzés

Ebben a leckében null értékű környezetet használt a kód lehetséges NullReferenceException előfordulásának azonosítására és megelőzésére.