Delen via


Zelfstudie: Uw ontwerpintentie duidelijker uitdrukken met nullbare en niet-nullbare referentietypen

Null-verwijzingstypen vullen referentietypen aan op dezelfde manier als dat null-waardetypen waardetypen aanvullen. U declareert een variabele als een null-verwijzingstype door een ? toe te voegen aan het type. string? vertegenwoordigt bijvoorbeeld een nullbare string. U kunt deze nieuwe typen gebruiken om uw ontwerpintentie duidelijker uit te drukken: sommige variabelen moeten altijd een waarde hebben , terwijl andere mogelijk een waarde missen.

In deze handleiding leer je hoe je:

  • Nullable en niet-nullable referentietypen opnemen in uw ontwerpen
  • Schakel nulbare verwijzingstypecontroles in heel uw code in.
  • Schrijf code waarbij de compiler deze ontwerpbeslissingen afdwingt.
  • Gebruik de nullable-referentiefunctie in uw eigen ontwerpen

Voorwaarden

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

Nullbare referentietypen opnemen in uw ontwerpen

In deze zelfstudie bouwt u een bibliotheek met modellen voor het uitvoeren van een enquête. De code maakt gebruik van zowel null-referentietypen als niet-null-referentietypen om de werkelijke concepten weer te geven. De enquêtevragen kunnen nooit null zijn. Een respondent geeft misschien liever geen antwoord op een vraag. In dit geval zijn de antwoorden mogelijk null.

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

De toepassing maken en nullable referentietypen inschakelen

Maak een nieuwe consoletoepassing in Visual Studio of vanaf de opdrachtregel met behulp van dotnet new console. Geef de toepassing een naam NullableIntroduction. Nadat u de toepassing hebt gemaakt, moet u opgeven dat het volledige project wordt gecompileerd in een ingeschakelde null-aantekeningscontext. Open het bestand .csproj en voeg een Nullable element toe aan het PropertyGroup-element. Stel de waarde ervan in op enable. U moet de functie nullable referentietypen inschakelen in projecten die zijn gemaakt vóór C# 11/.NET 7. Zodra de functie is ingeschakeld, worden bestaande declaraties van referentievariabelen niet-nullable verwijzingstypen. Hoewel deze beslissing helpt bij het vinden van problemen waarbij bestaande code mogelijk niet de juiste null-controles heeft, is het mogelijk dat deze niet nauwkeurig overeenkomt met uw oorspronkelijke ontwerpintentie:

<Nullable>enable</Nullable>

De typen voor de toepassing ontwerpen

Voor deze enquêtetoepassing moeten deze klassen worden gemaakt:

  • Een klasse die de lijst met vragen modellt.
  • Een klasse die een lijst met personen modelleert voor de enquête.
  • Een klasse die de antwoorden modelleert van een persoon die de enquête heeft ingevuld.

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

  • De vragen die deel uitmaken van de enquête kunnen nooit null zijn: het is niet logisch om een lege vraag te stellen.
  • De respondenten kunnen nooit null zijn. U wilt personen bijhouden die u hebt gecontacteerd, zelfs respondenten die hebben geweigerd deel te nemen.
  • Elk antwoord op een vraag kan null zijn. Respondenten kunnen weigeren om enkele of alle vragen te beantwoorden.

Mogelijk bent u zo gewend aan verwijzingstypen die null-waarden toestaan dat u andere kansen om niet-nullable exemplaren te declareren mogelijk mist:

  • De vragenlijst moet geen null-waarden accepteren.
  • De verzameling respondenten mag geen null-waarde bevatten.

Terwijl u de code schrijft, ziet u dat een niet-nullable verwijzingstype als de standaardwaarde voor verwijzingen veelvoorkomende fouten voorkomt die kunnen leiden tot NullReferenceExceptions. Een les uit deze zelfstudie is dat u beslissingen hebt genomen over welke variabelen wel of niet konden 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 voltooide enquêtegrootte het doelnummer bereikt.
  4. Schrijft belangrijke statistieken over de enquêteantwoorden.

De enquête opstellen met nullable en niet-nullable referentietypen

Met de eerste code die u schrijft, wordt de enquête gemaakt. U schrijft klassen om een enquêtevraag en een enquêteuitvoering te modelleren. Uw enquête heeft drie soorten vragen, onderscheiden van de indeling van het antwoord: Ja/Nee-antwoorden, nummerantwoorden en tekstantwoorden. Maak een public SurveyQuestion-klasse:

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

De compiler interpreteert elke referentietypevariabele declaratie als een niet-nulleerbaar referentietype voor code in een context met ingeschakelde nul-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 niet hebt geïnitialiseerd QuestionText, geeft de compiler een waarschuwing uit dat een niet-nullable eigenschap niet is geïnitialiseerd. Voor uw ontwerp moet de vraagtekst niet null zijn, dus u voegt een constructor toe om deze te initialiseren en de QuestionType waarde ook. 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 niet-null-referentietype, dus de compiler geeft geen waarschuwingen.

Maak vervolgens een public klasse met de naam SurveyRun. Deze klasse bevat een lijst met SurveyQuestion objecten en methoden voor het toevoegen van vragen 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 of geeft de compiler een waarschuwing. Er zijn geen null-controles in de tweede overbelasting van de functie AddQuestion omdat de compiler helpt het niet-nullable contract af te dwingen; u hebt die variabele namelijk gedeclareerd als niet-nullable. Hoewel de compiler waarschuwt voor mogelijke null-toewijzingen, zijn runtime-null-waarden nog steeds mogelijk. Voor openbare API's kunt u overwegen om argumentvalidatie toe te voegen, zelfs voor niet-null-referentietypen, omdat clientcode mogelijk geen null-referentietypen heeft ingeschakeld of opzettelijk null kan doorgeven.

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 voor nullable annotaties bevindt, krijgt u waarschuwingen wanneer u null doorgeeft aan een methode die een niet-nullbaar verwijzingstype verwacht. Probeer het 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 objecten vertegenwoordigen personen die worden 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 dat een respondent niet heeft beantwoord.
  3. Herhaal dit totdat voldoende respondenten de enquête beantwoorden.

U hebt een klasse nodig om een antwoord op een enquête weer te geven, dus voeg deze nu toe. Schakel null-ondersteuning 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 aan de vragen in de enquête. Deze verantwoordelijkheid heeft een paar 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 ontbreekt mogelijk ook (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>?, waarmee wordt aangegeven dat deze mogelijk null is. U gebruikt de nieuwe taalfunctie om uw ontwerpintentie te declareren, zowel voor de compiler als voor iedereen die uw code later leest. Als u ooit surveyResponses dereferentie uitvoert 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 of de surveyResponses variabele is ingesteld op een niet-null-waarde in de voorgaande code.

Als u null gebruikt voor ontbrekende antwoorden, wordt een belangrijk punt voor het werken met null-referentietypen gemarkeerd: uw doel is niet om alle null waarden uit uw programma te verwijderen. In plaats daarvan is het uw doel om ervoor te 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. Als u alle null waarden probeert te verwijderen, wordt een andere manier gedefinieerd om die ontbrekende waarden uit te drukken zonder null.

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

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

Hier nogmaals, uw keuze van een nullable List<SurveyResponse>? geeft 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 voldoende toestemming is.

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

Onderzoeken van enquêteantwoorden

De laatste stap is 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 nullable en niet-nullable verwijzingstypen. Begin met het toevoegen van de volgende twee expressielichaamsleden aan de klasse SurveyResponse:

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

Omdat surveyResponses een nullable referentietype is, zijn null-controles nodig voordat het wordt gedereferentieerd. De Answer-methode retourneert een niet-nullable string, dus moeten we het geval van een ontbrekend antwoord afdekken met behulp van de null-samenvoegoperator.

Voeg deze drie expressielichaamleden vervolgens toe aan de klasse SurveyRun.

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 null kan zijn, maar de retourwaarde kan niet null zijn. Als u deze expressie wijzigt door de ?? en de volgende lege reeks te verwijderen, waarschuwt de compiler u dat de methode mogelijk null zou kunnen retourneren en dat zijn retourtype een niet-null-type retourneert.

Voeg ten slotte de volgende lus toe onder aan de methode Main:

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 in deze code nodig omdat u de onderliggende interfaces zo hebt ontworpen dat ze allemaal niet-nullable verwijzingstypen retourneren. De statische analyse van de compiler helpt ervoor te zorgen dat deze ontwerpcontracten worden gevolgd.

De code ophalen

U kunt de code voor de voltooide handleiding verkrijgen uit onze voorbeelden repository in de map csharp/NullableIntroduction.

Probeer uit door de typedeclaraties te wijzigen tussen nullable en non-nullable referentietypen. Bekijk hoe dat verschillende waarschuwingen genereert om ervoor te zorgen dat u niet per ongeluk een nulldereferentie maakt.

Volgende stappen

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