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

Null-verwijzingstypen vormen een aanvulling op verwijzingstypen op dezelfde manier waarop null-waardetypen waardetypen aanvullen. U declareert een variabele als een verwijzingstype waarvoor 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 missen mogelijk een waarde.

In deze zelfstudie leert u het volgende:

  • Referentietypen met en zonder null-waarde opnemen in uw ontwerpen
  • Schakel controles van verwijzingstypen op basis van null in uw code in.
  • Schrijf code waar de compiler deze ontwerpbeslissingen afdwingt.
  • De referentiefunctie voor null gebruiken in uw eigen ontwerpen

Vereisten

U moet uw computer instellen voor het uitvoeren van .NET, 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, inclusief Visual Studio of de .NET CLI.

Verwijzingstypen met null-waarden opnemen in uw ontwerpen

In deze zelfstudie bouwt u een bibliotheek die het uitvoeren van een enquête modelleert. De code gebruikt zowel nullable verwijzingstypen als niet-nullable verwijzingstypen om de werkelijke concepten weer te geven. De enquêtevragen kunnen nooit null zijn. Een respondent geeft er mogelijk de voorkeur aan geen vraag te beantwoorden. 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 . Zodra u de toepassing hebt gemaakt, moet u opgeven dat het hele project wordt gecompileerd in een ingeschakelde context met 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 voor verwijzingstypen waarvoor null kan worden gebruikt in projecten die ouder zijn dan C# 11. Dat komt omdat zodra de functie is ingeschakeld, bestaande declaraties van referentievariabelen niet-null-verwijzingstypen worden. 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 een aantal klassen worden gemaakt:

  • Een klasse die de lijst met vragen modelleert.
  • Een klasse die een lijst modelleert met personen met wie contact is opgenomen voor de enquête.
  • Een klasse die de antwoorden van een persoon die aan de enquête heeft deelgenomen, modelleert.

Deze typen maken gebruik van zowel null- 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 heeft geen zin om een lege vraag te stellen.
  • De respondenten kunnen nooit null zijn. U wilt personen bijhouden met wie u contact hebt opgenomen, zelfs respondenten die hebben geweigerd om deel te nemen.
  • Elk antwoord op een vraag kan null zijn. Respondenten kunnen weigeren om sommige of alle vragen te beantwoorden.

Als u in C# hebt geprogrammeerd, bent u misschien zo gewend aan verwijzingstypen die waarden toestaan null dat u mogelijk andere mogelijkheden hebt gemist om niet-nullable exemplaren te declareren:

  • De verzameling vragen mag niet null zijn.
  • De verzameling respondenten mag niet null zijn.

Terwijl u de code schrijft, ziet u dat een verwijzingstype dat niet kan worden gebruikt als standaard 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 heeft drie typen vragen, die worden onderscheiden door de indeling van het antwoord: Ja/Nee-antwoorden, Aantal antwoorden en Tekstantwoorden. public SurveyQuestion Een klasse maken:

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

De compiler interpreteert de declaratie van elke variabele van het verwijzingstype als een verwijzingstype dat niet null kan worden gebruikt voor code in een ingeschakelde context met 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 dat een eigenschap die niet kan worden gebruikt om null te gebruiken, niet is geïnitialiseerd. Uw ontwerp vereist dat de vraagtekst niet null is, dus voegt u een constructor toe om deze en de QuestionType waarde te initialiseren. 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 waarvoor geen null-waarde 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 overload van AddQuestion omdat ze niet nodig zijn: U hebt die variabele als niet-null-compatibel aangegeven. 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 annotatiecontext met null-waarden bevindt, krijgt u waarschuwingen wanneer u doorgeeft null aan een methode waarvoor een verwijzingstype zonder null-waarde wordt verwacht. Probeer het door de volgende regel toe te voegen aan Main:

surveyRun.AddQuestion(QuestionType.Text, default);

Respondenten maken en antwoorden op de enquête krijgen

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 op te geven dat een respondent niet heeft geantwoord.
  3. Herhaal dit totdat voldoende respondenten de enquête hebben beantwoord.

U hebt een klasse nodig om een antwoord op een enquête weer te geven, dus voeg dat nu toe. Schakel ondersteuning voor null-waarden in. Voeg een Id eigenschap en een constructor toe waarmee deze wordt geïnitialiseerd, 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 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 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 wilt uitvoeren zonder eerst op de null waarde te controleren, krijgt u een waarschuwing over de compiler. 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 de retourhandtekening een niet-null-compatibel 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: