Alıştırma - Null güvenlik stratejileri uygulama

Tamamlandı

Önceki ünitede null atanabilirlik amacınızı kodda ifade etme hakkında bilgi edinmişsinizdir. Bu ünitede öğrendiklerini mevcut bir C# projesine uygulayacaksınız.

Not

Bu modülde yerel geliştirme için .NET CLI (Komut Satırı Arabirimi) ve Visual Studio Code kullanılır. Bu modülü tamamladıktan sonra Visual Studio (Windows), Mac için Visual Studio (macOS) veya Visual Studio Code (Windows, Linux ve macOS) kullanarak geliştirmeye devam ederek kavramları uygulayabilirsiniz.

Bu modülde .NET 6.0 SDK kullanılır. Tercih ettiğiniz terminalde aşağıdaki komutu çalıştırarak .NET 6.0'ın yüklü olduğundan emin olun:

dotnet --list-sdks

Aşağıdakine benzer bir çıkış görüntülenir:

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

ile 6 başlayan bir sürümün listelendiğinden emin olun. Hiçbiri listelenmiyorsa veya komut bulunamazsa en son .NET 6.0 SDK'sını yükleyin.

Örnek kodu alma ve inceleme

  1. Komut terminalinde örnek GitHub deposunu kopyalayın ve kopyalanan dizine geçin.

    git clone https://github.com/microsoftdocs/mslearn-csharp-null-safety
    cd mslearn-csharp-null-safety
    
  2. Visual Studio Code'da proje dizinini açın.

    code .
    
  3. komutunu kullanarak dotnet run örnek projeyi çalıştırın.

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

    Bu, bir NullReferenceException atılmasıyla sonuçlanır.

    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
    

    Yığın izlemesi, özel durumun .\src\ContosoPizza.Service\Program.cs içindeki 13. satırda oluştuğuna işaret eder. 13. satırda Add yöntemi özelliğinde çağrılır pizza.Cheeses . olduğundan pizza.Cheesesnull, bir NullReferenceException oluşturulur.

    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!
    */
    

Boş değer atanabilir bağlamı etkinleştirme

Şimdi boş değer atanabilir bir bağlamı etkinleştirip derleme üzerindeki etkisini inceleyeceksiniz.

  1. src/ContosoPizza.Service/ContosoPizza.Service.csproj içinde, vurgulanan satırı ekleyin ve değişikliklerinizi kaydedin:

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

    Yukarıdaki değişiklik, projenin tamamı ContosoPizza.Service için null atanabilir bağlamı etkinleştirir.

  2. src/ContosoPizza.Models/ContosoPizza.Models.csproj içinde, vurgulanan satırı ekleyin ve değişikliklerinizi kaydedin:

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

    Yukarıdaki değişiklik, projenin tamamı ContosoPizza.Models için null atanabilir bağlamı etkinleştirir.

  3. komutunu kullanarak dotnet build örnek çözümü oluşturun.

    dotnet build
    

    Derleme 2 uyarıyla başarılı olur.

    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. komutunu kullanarak örnek çözümü dotnet build oluşturun.

    dotnet build
    

    Bu kez derleme hiçbir hata veya uyarı olmadan başarılı olur. Önceki derleme, uyarılarla başarıyla tamamlandı. Kaynak değişmediğinden derleme işlemi derleyiciyi yeniden çalıştırmaz. Derleme derleyiciyi çalıştırmadığından hiçbir uyarı yoktur.

    İpucu

    komutundan önce komutunu kullanarak projedeki tüm derlemelerin yeniden derlenmesine dotnet cleandotnet buildzorlayabilirsiniz.

  5. .csproj dosyalarına vurgulanan satırları ekleyin ve değişikliklerinizi kaydedin.

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

    Önceki değişiklikler, derleyiciye bir uyarıyla karşılaşıldığında derlemenin başarısız olmasını bildirir.

    İpucu

    kullanımı <TreatWarningsAsErrors> isteğe bağlıdır. Ancak hiçbir uyarıyı gözden kaçırmamanızı sağladığı için bunu öneririz.

  6. komutunu kullanarak dotnet build örnek çözümü oluşturun.

    dotnet build
    

    Derleme 2 hatayla başarısız oluyor.

    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
    

    Uyarıları hata olarak ele alırken uygulama artık derlenmez. Bu durum aslında istenen bir durumdur çünkü hata sayısı azdır ve bunları hızla ele alacağız. İki hata (CS8618), henüz başlatılmamış null atanamaz olarak bildirilen özellikler olduğunu size bildirir.

Hataları düzeltme

Null atanabilirlikle ilgili uyarıları/hataları çözmek için birçok taktik vardır. Bazı Örnekler:

  • Oluşturucu parametreleri olarak boş değer atanamayan peynir ve topping koleksiyonu gerektirme
  • Özelliğin get/set yolunu kesme ve denetim null ekleme
  • Özelliklerin null atanabilir olma amacını ifade etme
  • Özellik başlatıcılarını kullanarak koleksiyonu varsayılan (boş) satır içi değerle başlatın
  • Özelliğine oluşturucuda varsayılan (boş) bir değer atayın
  1. Özelliğindeki hatayı Pizza.Cheeses düzeltmek için Pizza.cs'da özellik tanımını değiştirerek bir null denetim ekleyin. Peynirsiz pizza değil, değil mi?

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

    Önceki kodda:

    • adlı getve set özellik erişimcilerini kesmeye _cheeses yardımcı olmak için yeni bir yedekleme alanı eklenir. Null atanabilir (?) olarak bildirilip başlatılmamış olarak bırakılır.
    • Erişimci get , null birleşim işlecini (?? kullanan bir ifadeyle eşlenir. Bu ifade, olmadığını _cheesesvarsayarak alanını döndürürnull. isenull, döndürmeden _cheesesönce öğesini new List<PizzaCheese>() atar_cheeses.
    • Erişimci set de bir ifadeye eşlenir ve null birleşim işlecini kullanır. Bir tüketici bir null değer ArgumentNullException atadığında oluşturulur.
  2. Tüm pizzalarda topping'ler olmadığından, null özellik için Pizza.Toppings geçerli bir değer olabilir. Bu durumda, null atanabilir olarak ifade etmek mantıklıdır.

    1. Pizza.csözellik tanımını null atanabilir olacak şekilde değiştirin.

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

      Toppings Özelliği artık null atanabilir olarak ifade edilir.

    2. Vurgulanan satırı ContosoPizza.Service\Program.cs ekleyin:

      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!
      */
      

    Yukarıdaki kodda null birleşim işleci, ise Toppingsöğesine atamak new List<PizzaTopping>(); için null kullanılır.

Tamamlanmış çözümü çalıştırma

  1. Tüm değişikliklerinizi kaydedin ve çözümü oluşturun.

    dotnet build
    

    Derleme hiçbir uyarı veya hata olmadan tamamlar.

  2. Uygulamayı çalıştırma.

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

    Uygulama tamamlanmaya (hata olmadan) çalışır ve aşağıdaki çıkışı görüntüler:

    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!
    

Özet

Bu ünitede, kodunuzda olası NullReferenceException oluşumları tanımlamak ve önlemek için null atanabilir bir bağlam kullandınız.