Zelfstudie: Uw ontwerpintentie duidelijker uitdrukken met verwijzingstypen die kunnen worden gebruikt voor null en niet-null

Null-verwijzingstypen vormen een aanvulling op referentietypen op dezelfde manier als null-waardetypen een aanvulling vormen op waardetypen. U declareert een variabele als een verwijzingstype dat null kan worden gebruikt door een ? toe te voegen aan het type. Vertegenwoordigt bijvoorbeeld string? een null-waarde string. U kunt deze nieuwe typen gebruiken om uw ontwerpintentie duidelijker uit te drukken: sommige variabelen moeten altijd een waarde hebben, andere kunnen een waarde missen.

In deze zelfstudie leert u het volgende:

  • Verwijzingstypen die null en niet null kunnen worden opgenomen in uw ontwerpen
  • Schakel controles van verwijzingstypen in op null in uw code.
  • Schrijf code waar de compiler deze ontwerpbeslissingen afdwingt.
  • De null-referentiefunctie gebruiken in uw eigen ontwerpen

Vereisten

U moet uw computer instellen om .NET uit te voeren, inclusief de C#-compiler. De C#-compiler is beschikbaar met Visual Studio 2022 of de .NET SDK.

In deze zelfstudie wordt ervan uitgegaan dat u bekend bent met C# en .NET, met inbegrip van Visual Studio of de .NET CLI.

Referentietypen met null opnemen in uw ontwerpen

In deze zelfstudie bouwt u een bibliotheek die het uitvoeren van een enquête modelleert. De code maakt gebruik van zowel null-verwijzingstypen als niet-null-verwijzingstypen om de werkelijke concepten weer te geven. De enquêtevragen kunnen nooit null zijn. Het kan zijn dat een respondent liever geen vraag beantwoordt. De antwoorden kunnen in dit geval zijn null .

De code die u voor dit voorbeeld schrijft, drukt die intentie uit en de compiler dwingt die intentie af.

De toepassing maken en null-referentietypen inschakelen

Maak een nieuwe consoletoepassing in Visual Studio of vanaf de opdrachtregel met behulp van dotnet new console. Geef de toepassing NullableIntroductionde naam . Nadat u de toepassing hebt gemaakt, moet u opgeven dat het hele project wordt gecompileerd in een ingeschakelde context voor null-aantekening. Open het .csproj-bestand en voeg een Nullable -element toe aan het PropertyGroup -element. Stel de waarde op enable. U moet zich aanmelden voor de functie verwijzingstypen die null kunnen worden gebruikt in projecten die ouder zijn dan C# 11. Wanneer de functie is ingeschakeld, worden bestaande declaraties van referentievariabelen namelijk niet-null-bare verwijzingstypen. Hoewel deze beslissing helpt bij het vinden van problemen waarbij bestaande code mogelijk niet de juiste null-controles heeft, komt deze mogelijk niet nauwkeurig overeen met uw oorspronkelijke ontwerpintentie:

<Nullable>enable</Nullable>

Vóór .NET 6 bevatten nieuwe projecten het Nullable element niet. Vanaf .NET 6 bevatten nieuwe projecten het <Nullable>enable</Nullable> element in het projectbestand.

De typen voor de toepassing ontwerpen

Voor deze enquêtetoepassing moet u een aantal klassen maken:

  • Een klasse die de lijst met vragen modelleert.
  • Een klasse die een lijst modelleert met personen die contact hebben opgenomen voor de enquête.
  • Een klasse die de antwoorden van een persoon die de enquête heeft gevolgd, modelleert.

Deze typen maken gebruik van zowel null- als niet-null-referentietypen om aan te geven welke leden vereist zijn en welke leden optioneel zijn. Null-verwijzingstypen communiceren die ontwerpintentie duidelijk:

  • De vragen die deel uitmaken van de enquête kunnen nooit null zijn: het heeft geen zin om een lege vraag te stellen.
  • De respondenten kunnen nooit null zijn. U wilt de personen bijhouden waarmee u contact hebt opgenomen, zelfs respondenten die hebben geweigerd deel te nemen.
  • Elk antwoord op een vraag kan null zijn. Respondenten kunnen weigeren om sommige of alle vragen te beantwoorden.

Als u C# hebt geprogrammeerd, bent u mogelijk zo gewend aan verwijzingen naar typen die waarden toestaan null , dat u andere mogelijkheden hebt gemist om niet-null-bare exemplaren te declareren:

  • De verzameling vragen moet niet null-compatibel zijn.
  • De verzameling respondenten moet niet nullable zijn.

Terwijl u de code schrijft, ziet u dat een verwijzingstype dat niet null is als standaardwaarde voor verwijzingen veelvoorkomende fouten voorkomt die tot NullReferenceExceptions kunnen leiden. Een les uit deze zelfstudie is dat u beslissingen hebt genomen over welke variabelen wel of niet kunnen zijn null. De taal heeft geen syntaxis opgegeven om deze beslissingen uit te drukken. Nu wel.

De app die u bouwt, voert de volgende stappen uit:

  1. Hiermee maakt u een enquête en voegt u er vragen aan toe.
  2. Hiermee maakt u een pseudo-willekeurige set respondenten voor de enquête.
  3. Neemt contact op met respondenten totdat de grootte van de voltooide enquête het doelnummer heeft bereikt.
  4. Schrijft belangrijke statistieken over de antwoorden van de enquête.

De enquête samenstellen met verwijzingstypen die kunnen worden gebruikt voor null en niet-null

De eerste code die u schrijft, maakt de enquête. U schrijft klassen om een enquêtevraag en een enquêteuitvoering te modelleren. Uw enquête bevat drie soorten vragen, die worden onderscheiden door de indeling van het antwoord: Ja/Nee-antwoorden, antwoorden op getallen en antwoorden op tekst. Een klasse maken public SurveyQuestion :

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

De compiler interpreteert elke variabeledeclaratie van het verwijzingstype als een niet-nullbaar verwijzingstype voor code in een ingeschakelde context voor null-aantekening. U kunt uw eerste waarschuwing zien door eigenschappen toe te voegen voor de vraagtekst en het type vraag, zoals wordt weergegeven in de volgende code:

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

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

Omdat u nog niet hebt geïnitialiseerd QuestionText, geeft de compiler een waarschuwing uit dat een eigenschap die niet null kan worden gebruikt, niet is geïnitialiseerd. Uw ontwerp vereist dat de vraagtekst niet null is, dus voegt u een constructor toe om deze te initialiseren, evenals de QuestionType waarde. De voltooide klassedefinitie ziet eruit als de volgende code:

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

Als u de constructor toevoegt, wordt de waarschuwing verwijderd. Het constructorargument is ook een verwijzingstype dat niet null kan worden gebruikt, zodat de compiler geen waarschuwingen geeft.

Maak vervolgens een public klasse met de naam SurveyRun. Deze klasse bevat een lijst met SurveyQuestion objecten en methoden om vragen toe te voegen aan de enquête, zoals wordt weergegeven in de volgende code:

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

Net als voorheen moet u het lijstobject initialiseren naar een niet-null-waarde, anders geeft de compiler een waarschuwing. Er zijn geen null-controles in de tweede overbelasting van AddQuestion omdat ze niet nodig zijn: u hebt die variabele als niet-null-compatibel gedeclareerd. De waarde mag niet zijn null.

Schakel over naar Program.cs in uw editor en vervang de inhoud van Main door de volgende regels code:

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?");

Omdat het hele project zich in een ingeschakelde context met null-aantekening bevindt, krijgt u waarschuwingen wanneer u een methode doorgeeft null die een verwijzingstype verwacht dat niet null kan worden gebruikt. Probeer het uit door de volgende regel toe te voegen aan Main:

surveyRun.AddQuestion(QuestionType.Text, default);

Respondenten maken en antwoorden krijgen op de enquête

Schrijf vervolgens de code waarmee antwoorden op de enquête worden gegenereerd. Dit proces omvat verschillende kleine taken:

  1. Bouw een methode waarmee respondentobjecten worden gegenereerd. Deze vertegenwoordigen personen die zijn gevraagd om de enquête in te vullen.
  2. Bouw logica om het stellen van de vragen aan een respondent te simuleren en antwoorden te verzamelen of te noteren die een respondent niet heeft beantwoord.
  3. Herhaal dit totdat voldoende respondenten de enquête hebben beantwoord.

U hebt een klasse nodig om een enquêteantwoord weer te geven, dus voeg dat nu toe. Schakel ondersteuning voor null in. Voeg een Id eigenschap en een constructor toe die deze initialiseert, zoals wordt weergegeven in de volgende code:

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

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

Voeg vervolgens een static methode toe om nieuwe deelnemers te maken door een willekeurige id te genereren:

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

De belangrijkste verantwoordelijkheid van deze klas is het genereren van de antwoorden voor een deelnemer op de vragen in de enquête. Deze verantwoordelijkheid bestaat uit een aantal stappen:

  1. Vraag om deelname aan de enquête. Als de persoon geen toestemming geeft, retourneert u een ontbrekend (of null) antwoord.
  2. Stel elke vraag en noteer het antwoord. Elk antwoord kan ook ontbreken (of null).

Voeg de volgende code toe aan uw SurveyResponse klasse:

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!";
    }
}

De opslag voor de antwoorden op de enquête is een Dictionary<int, string>?, wat aangeeft dat deze null kan zijn. U gebruikt de nieuwe taalfunctie om uw ontwerpintentie te declareren, zowel aan de compiler als aan iedereen die uw code later leest. Als u ooit de deductie surveyResponses doet zonder eerst op de null waarde te controleren, krijgt u een compilerwaarschuwing. U krijgt geen waarschuwing in de AnswerSurvey methode omdat de compiler kan bepalen dat de surveyResponses variabele hierboven is ingesteld op een niet-null-waarde.

Het gebruik null voor ontbrekende antwoorden markeert een belangrijk punt voor het werken met null-referentietypen: het doel is niet om alle null waarden uit uw programma te verwijderen. In plaats daarvan wilt u ervoor zorgen dat de code die u schrijft, de intentie van uw ontwerp uitdrukt. Ontbrekende waarden zijn een noodzakelijk concept om in uw code uit te drukken. De null waarde is een duidelijke manier om die ontbrekende waarden uit te drukken. Het verwijderen van alle null waarden leidt alleen tot het definiëren van een andere manier om die ontbrekende waarden uit te drukken zonder null.

Vervolgens moet u de PerformSurvey methode in de SurveyRun klasse schrijven. Voeg de volgende code toe in de SurveyRun klasse:

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

Ook hier geeft uw keuze voor een null-waarde List<SurveyResponse>? aan dat het antwoord mogelijk null is. Dit geeft aan dat de enquête nog niet aan respondenten is gegeven. U ziet dat respondenten worden toegevoegd totdat er genoeg toestemming hebben gegeven.

De laatste stap voor het uitvoeren van de enquête is het toevoegen van een aanroep om de enquête uit te voeren aan het einde van de Main methode:

surveyRun.PerformSurvey(50);

Enquêteantwoorden onderzoeken

De laatste stap bestaat uit het weergeven van enquêteresultaten. U voegt code toe aan veel van de klassen die u hebt geschreven. Deze code demonstreert de waarde van het onderscheiden van null-referentietypen en niet-null-referentietypen. Begin met het toevoegen van de volgende twee expressie-bodyied leden aan de SurveyResponse klasse:

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

Omdat surveyResponses een verwijzingstype met null-waarden is, zijn null-controles nodig voordat de verwijzing wordt opgeheven. De Answer methode retourneert een tekenreeks die niet null kan worden gebruikt. Daarom moeten we het geval van een ontbrekend antwoord behandelen met behulp van de operator null-coalescing.

Voeg vervolgens deze drie expressieleden toe aan de SurveyRun klasse:

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

Het AllParticipants lid moet er rekening mee houden dat de respondents variabele mogelijk null is, maar de retourwaarde niet null. Als u die expressie wijzigt door de ?? en de lege reeks die volgt te verwijderen, waarschuwt de compiler u dat de methode mogelijk retourneert null en dat de retourhandtekening een niet-nullbaar type retourneert.

Voeg ten slotte de volgende lus toe aan de onderkant van de Main methode:

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

U hebt geen null controles nodig in deze code, omdat u de onderliggende interfaces zo hebt ontworpen dat ze allemaal niet-nullbare verwijzingstypen retourneren.

Code ophalen

U kunt de code voor de voltooide zelfstudie ophalen uit onze opslagplaats met voorbeelden in de map csharp/NullableIntroduction .

Experimenteer door de typedeclaraties te wijzigen tussen verwijzingstypen die kunnen worden gebruikt voor null en niet-null-declaraties. Bekijk hoe dat verschillende waarschuwingen genereert om ervoor te zorgen dat u niet per ongeluk een nulldeductie maakt.

Volgende stappen

Meer informatie over het gebruik van null-referentietype bij het gebruik van Entity Framework: