Sdílet prostřednictvím


Návod: Vyjádřete svůj návrhový záměr jasněji pomocí nulovatelných a nenulovatelných referenčních typů

odkazové typy s možnou hodnotou Null doplňují typy odkazů stejným způsobem, jakým typy hodnot s možnou hodnotou null doplňují typy hodnot. Proměnnou deklarujete jako nulovatelný referenční typ připojením ? k typu. Například string? představuje nulovatelný string. Tyto nové typy můžete použít k srozumitelnějšímu vyjádření záměru návrhu: některé proměnné musí mít vždy hodnotu, jiné mohou chybět hodnotu.

V tomto kurzu se naučíte:

  • Začlenit nulovatelné a nenulovatelné referenční typy do návrhů
  • Povolte kontroly nulovatelného typu odkazu v celém kódu.
  • Napište kód, ve kterém kompilátor vynucuje tato rozhodnutí o návrhu.
  • Použijte funkci nulovatelné reference ve vlastních návrzích

Požadavky

Budete muset nastavit počítač tak, aby běžel na platformě .NET, včetně kompilátoru jazyka C#. Kompilátor jazyka C# je k dispozici v sadě Visual Studio 2022nebo .NET SDK.

V tomto kurzu se předpokládá, že znáte C# a .NET, včetně sady Visual Studio nebo rozhraní příkazového řádku .NET.

Začlenění referenčních typů s možnou hodnotou null do návrhů

V tomto kurzu vytvoříte knihovnu, která modeluje spuštění průzkumu. Kód používá nulovatelné odkazové typy i nenulovatelné odkazové typy k reprezentaci skutečných konceptů. Otázky průzkumu nemohou mít nikdy hodnotu null. Respondent nemusí chtít odpovědět na otázku. V tomto případě mohou být odpovědi null.

Kód, který napíšete pro tuto ukázku, tento záměr vyjadřuje a kompilátor tento záměr vynucuje.

Vytvořte aplikaci a povolte nullable reference type.

Vytvořte novou konzolovou aplikaci v sadě Visual Studio nebo z příkazového řádku pomocí dotnet new console. Pojmenujte aplikaci NullableIntroduction. Po vytvoření aplikace budete muset určit, že se celý projekt zkompiluje v povoleném kontextu poznámek s možnou hodnotou null. Otevřete soubor .csproj a do elementu PropertyGroup přidejte prvek Nullable. Nastavte jeho hodnotu na enable. Musíte aktivovat funkci nulovatelných referenčních typů v projektech s verzí C# starší než 11. Důvodem je, že jakmile je funkce zapnutá, existující deklarace odkazových proměnných se stanou nenulovatelnými odkazovými typy. I když toto rozhodnutí pomůže najít problémy, kdy stávající kód nemusí mít správné kontroly null, nemusí přesně odrážet původní záměr návrhu:

<Nullable>enable</Nullable>

Před rozhraním .NET 6 nové projekty nezahrnují prvek Nullable. Počínaje rozhraním .NET 6 zahrnují nové projekty do souboru projektu prvek <Nullable>enable</Nullable>.

Návrh typů pro aplikaci

Tato aplikace průzkumu vyžaduje vytvoření řady tříd:

  • Třída, která modeluje seznam otázek.
  • Třída, která modeluje seznam lidí kontaktovaných k průzkumu.
  • Třída, která modeluje odpovědi osoby, která se zúčastnila průzkumu.

Tyto typy budou využívat nullable a non-nullable referenční typy k vyjádření, které členy jsou povinné a které jsou nepovinné. Odkazové typy s možnou hodnotou null jasně komunikují o záměru návrhu:

  • Otázky, které jsou součástí průzkumu, nemohou být nikdy null: Nemá smysl položit prázdnou otázku.
  • Respondenti nemohou mít nikdy hodnotu null. Budete chtít sledovat osoby, které jste kontaktovali, i respondenty, kteří odmítli účast.
  • Jakákoli odpověď na otázku může mít hodnotu null. Respondenti můžou odmítnout odpovědi na některé nebo všechny otázky.

Pokud jste programovali v jazyce C#, můžete být natolik zvyklí na odkazové typy, které umožňují hodnoty typu null, že jste mohli přehlédnout jiné příležitosti k deklaraci nenulovatelných instancí:

  • Kolekce otázek by měla být nenulová.
  • Kolekce respondentů by měla být nenulovatelná.

Při psaní kódu uvidíte, že typ odkazu bez hodnoty null jako výchozí pro odkazy zabraňuje běžným chybám, které by mohly vést k NullReferenceException. Jednou z lekcí tohoto kurzu je, že jste se rozhodli, které proměnné by mohly nebo nemohly být null. Jazyk neposkytoval syntaxi pro vyjádření těchto rozhodnutí. Teď to funguje.

Aplikace, kterou vytvoříte, provede následující kroky:

  1. Vytvoří průzkum a přidá do něj otázky.
  2. Vytvoří pseudonáhodnou sadu respondentů pro průzkum.
  3. Kontaktuje respondenty, dokud dokončená velikost průzkumu nedosáhne cílového čísla.
  4. Zapíše důležité statistiky o odpovědích na průzkum.

Sestavení průzkumu s nullovatelnými a nenullovatelnými odkazovými typy

První kód, který napíšete, vytvoří průzkum. Napíšete třídy pro modelování otázky průzkumu a běhu průzkumu. Průzkum má tři typy otázek, odlišují se podle formátu odpovědi: Ano/Ne odpovědi, číselné odpovědi a textové odpovědi. Vytvořte třídu public SurveyQuestion:

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

Kompilátor interpretuje každou deklaraci proměnné referenčního typu jako nenulový referenční typ pro kód v kontextu povolených anotací s hodnotou null. První upozornění můžete zobrazit přidáním vlastností textu otázky a typu otázky, jak je znázorněno v následujícím kódu:

namespace NullableIntroduction
{
    public enum QuestionType
    {
        YesNo,
        Number,
        Text
    }

    public class SurveyQuestion
    {
        public string QuestionText { get; }
        public QuestionType TypeOfQuestion { get; }
    }
}

Protože jste neicializovali QuestionText, kompilátor vydá upozornění, že vlastnost, která není nullable, nebyla inicializována. Návrh vyžaduje, aby text otázky byl nenulový, takže přidáte konstruktor, který ho inicializuje a také hodnotu QuestionType. Hotová definice třídy vypadá jako následující kód:

namespace NullableIntroduction;

public enum QuestionType
{
    YesNo,
    Number,
    Text
}

public class SurveyQuestion
{
    public string QuestionText { get; }
    public QuestionType TypeOfQuestion { get; }

    public SurveyQuestion(QuestionType typeOfQuestion, string text) =>
        (TypeOfQuestion, QuestionText) = (typeOfQuestion, text);
}

Přidání konstruktoru odebere upozornění. Argument konstruktoru je také nenulový odkazový typ, takže kompilátor nevystavuje žádná upozornění.

Dále vytvořte třídu public s názvem SurveyRun. Tato třída obsahuje seznam SurveyQuestion objektů a metod pro přidání otázek do průzkumu, jak je znázorněno v následujícím kódu:

using System.Collections.Generic;

namespace NullableIntroduction
{
    public class SurveyRun
    {
        private List<SurveyQuestion> surveyQuestions = new List<SurveyQuestion>();

        public void AddQuestion(QuestionType type, string question) =>
            AddQuestion(new SurveyQuestion(type, question));
        public void AddQuestion(SurveyQuestion surveyQuestion) => surveyQuestions.Add(surveyQuestion);
    }
}

Stejně jako předtím musíte inicializovat objekt seznamu na hodnotu, která není null nebo kompilátor vydá upozornění. Ve druhém přetížení AddQuestion nejsou žádné kontroly na nulové hodnoty, protože nejsou potřeba: Deklarovali jste totiž tuto proměnnou jako nenulovatelnou. Její hodnota nemůže být null.

Přepněte do Program.cs v editoru a nahraďte obsah Main následujícími řádky kódu:

var surveyRun = new SurveyRun();
surveyRun.AddQuestion(QuestionType.YesNo, "Has your code ever thrown a NullReferenceException?");
surveyRun.AddQuestion(new SurveyQuestion(QuestionType.Number, "How many times (to the nearest 100) has that happened?"));
surveyRun.AddQuestion(QuestionType.Text, "What is your favorite color?");

Protože celý projekt je v povoleném kontextu poznámek s možnou hodnotou null, zobrazí se upozornění, když předáte null jakékoli metodě, která očekává nenulový odkazový typ. Zkuste to přidáním následujícího řádku do Main:

surveyRun.AddQuestion(QuestionType.Text, default);

Vytvářejte respondenty a získejte odpovědi na průzkum.

Dále napište kód, který generuje odpovědi na průzkum. Tento proces zahrnuje několik malých úloh:

  1. Vytvořte metodu, která generuje objekty respondenta. Jedná se o osoby, které byly požádány, aby vyplnily průzkum.
  2. Sestavte logiku pro simulaci kladení otázek respondentovi a shromažďování odpovědí nebo zaznamenání, že respondent neodpověděl.
  3. Opakujte, dokud nezodpověděli dostatek respondentů na průzkum.

Budete potřebovat třídu, která bude reprezentovat odpověď průzkumu, takže ji přidejte. Povolte podporu s možnou hodnotou null. Přidejte vlastnost Id a konstruktor, který ji inicializuje, jak je znázorněno v následujícím kódu:

namespace NullableIntroduction
{
    public class SurveyResponse
    {
        public int Id { get; }

        public SurveyResponse(int id) => Id = id;
    }
}

Dále přidejte metodu static pro vytvoření nových účastníků vygenerováním náhodného ID:

private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());

Hlavní odpovědností této třídy je vygenerovat odpovědi účastníka na otázky v průzkumu. Tato odpovědnost má několik kroků:

  1. Požádejte o účast v průzkumu. Pokud osoba nesouhlasí, vraťte chybějící (nebo nulovou) odpověď.
  2. Položte každou otázku a nahrajte odpověď. Každá odpověď může také chybět (nebo null).

Do třídy SurveyResponse přidejte následující kód:

private Dictionary<int, string>? surveyResponses;
public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
    if (ConsentToSurvey())
    {
        surveyResponses = new Dictionary<int, string>();
        int index = 0;
        foreach (var question in questions)
        {
            var answer = GenerateAnswer(question);
            if (answer != null)
            {
                surveyResponses.Add(index, answer);
            }
            index++;
        }
    }
    return surveyResponses != null;
}

private bool ConsentToSurvey() => randomGenerator.Next(0, 2) == 1;

private string? GenerateAnswer(SurveyQuestion question)
{
    switch (question.TypeOfQuestion)
    {
        case QuestionType.YesNo:
            int n = randomGenerator.Next(-1, 2);
            return (n == -1) ? default : (n == 0) ? "No" : "Yes";
        case QuestionType.Number:
            n = randomGenerator.Next(-30, 101);
            return (n < 0) ? default : n.ToString();
        case QuestionType.Text:
        default:
            switch (randomGenerator.Next(0, 5))
            {
                case 0:
                    return default;
                case 1:
                    return "Red";
                case 2:
                    return "Green";
                case 3:
                    return "Blue";
            }
            return "Red. No, Green. Wait.. Blue... AAARGGGGGHHH!";
    }
}

Úložiště odpovědí na průzkum je Dictionary<int, string>?, což znamená, že může mít hodnotu null. Pomocí nové funkce jazyka deklarujete záměr návrhu, a to jak kompilátoru, tak každému, kdo kód později přečte. Pokud byste někdy dereferencovali surveyResponses bez předchozí kontroly hodnoty null, zobrazí se upozornění kompilátoru. V metodě AnswerSurvey se nezobrazí upozornění, protože kompilátor může určit, že proměnná surveyResponses byla nastavena na nenulovou hodnotu, jak je výše uvedeno.

Použití null pro chybějící odpovědi zvýrazní klíčový bod pro práci s odkazovými typy s možnou hodnotou null: vaším cílem není odebrat všechny null hodnoty z programu. Vaším cílem je zajistit, aby kód, který napíšete, vyjadřoval záměr návrhu. Chybějící hodnoty jsou nezbytným konceptem pro vyjádření v kódu. Hodnota null je jasný způsob, jak tyto chybějící hodnoty vyjádřit. Pokus o odebrání všech hodnot null vede pouze k definování jiného způsobu vyjádření chybějících hodnot bez null.

Dále je potřeba napsat metodu PerformSurvey ve třídě SurveyRun. Do třídy SurveyRun přidejte následující kód:

private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
    int respondentsConsenting = 0;
    respondents = new List<SurveyResponse>();
    while (respondentsConsenting < numberOfRespondents)
    {
        var respondent = SurveyResponse.GetRandomId();
        if (respondent.AnswerSurvey(surveyQuestions))
            respondentsConsenting++;
        respondents.Add(respondent);
    }
}

Zde opět volba nulovatelného List<SurveyResponse>? naznačuje, že odpověď může být nulová. To znamená, že průzkum zatím nebyl udělen žádným respondentům. Všimněte si, že respondenti jsou přidáváni, dokud dostatečný počet nesouhlasí.

Posledním krokem ke spuštění průzkumu je přidání volání k provedení průzkumu na konci metody Main:

surveyRun.PerformSurvey(50);

Prozkoumání odpovědí na průzkum

Posledním krokem je zobrazení výsledků průzkumu. Do mnoha tříd, které jste napsali, přidáte kód. Tento kód ukazuje hodnotu rozlišení mezi nulovatelnými a nenulovatelnými odkazy. Začněte přidáním následujících dvou členů, využívajících výrazové tělo, do třídy SurveyResponse:

public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";

Vzhledem k tomu, že surveyResponses je typ odkazu s možnou hodnotou null, jsou před zrušením odkazu nutné kontroly null. Metoda Answer vrátí nenulový řetězec, takže musíme pokrýt případ chybějící odpovědi pomocí operátoru null-coalescing.

Dále přidejte tyto tři členy s výrazovým tělem do třídy SurveyRun:

public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];

Člen AllParticipants musí vzít v úvahu, že proměnná respondents může mít hodnotu null, ale návratová hodnota nemůže být null. Pokud tento výraz změníte odebráním ?? a prázdné sekvence, která následuje, kompilátor vás upozorní, že metoda může vrátit null a jeho návratový podpis vrátí nenulový typ.

Nakonec do dolní části metody Main přidejte následující smyčku:

foreach (var participant in surveyRun.AllParticipants)
{
    Console.WriteLine($"Participant: {participant.Id}:");
    if (participant.AnsweredSurvey)
    {
        for (int i = 0; i < surveyRun.Questions.Count; i++)
        {
            var answer = participant.Answer(i);
            Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} : {answer}");
        }
    }
    else
    {
        Console.WriteLine("\tNo responses");
    }
}

V tomto kódu nepotřebujete žádné null kontroly, protože jste navrhli základní rozhraní tak, aby všechny vracely odkazové typy bez hodnoty null.

Získání kódu

Kód pro dokončený tutoriál získáte z našeho ukázkového repozitáře ve složce csharp/NullableIntroduction.

Experimentujte změnou deklarací typu mezi odkazovými typy s možnou hodnotou null a nenulovou. Podívejte se, jak se generují různá upozornění, abyste se ujistili, že omylem nedereferencujete null.

Další kroky

Naučte se používat referenční typ s možnou hodnotou null při použití entity Framework: