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, andere mogelijk een waarde missen.
In deze zelfstudie leert u het volgende:
- 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
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.
Null-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 null-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 hele project wordt gecompileerd in een ingeschakelde context voor null-aantekening. Open het bestand .csproj en voeg een Nullable
element toe aan het PropertyGroup
-element. Stel de waarde ervan in op enable
. U moet de nullable-verwijzingstypen functie inschakelen in projecten die zijn gemaakt in versies vóór C# 11. Zodra de functie is ingeschakeld, worden bestaande declaraties van referentievariabelen niet-null-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 de 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 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 duidelijk 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 om deel te nemen.
- Elk antwoord op een vraag kan null zijn. Respondenten kunnen weigeren om enkele of alle vragen te beantwoorden.
Als u in C# hebt geprogrammeerd, bent u misschien zo gewend aan verwijzingstypen die null
waarden toestaan, dat u de kans over het hoofd hebt gezien om niet-nullable instanties te declareren.
- De verzameling vragen moet niet nullable zijn.
- De verzameling respondenten mag geen null-waarde bevatten.
Terwijl u de code schrijft, ziet u dat een niet-nullable verwijzingstype als standaard 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 kunnen worden 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:
- Hiermee maakt u een enquête en voegt u er vragen aan toe.
- Hiermee maakt u een pseudo-willekeurige set respondenten voor de enquête.
- Neemt contact op met respondenten totdat de voltooide enquêtegrootte het doelnummer bereikt.
- Schrijft belangrijke statistieken over de enquêteantwoorden.
De enquête bouwen met null-en niet-null-verwijzingstypen
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 QuestionText
niet hebt geïnitialiseerd, geeft de compiler een waarschuwing uit dat er geen niet-nullbare eigenschap 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 overload van AddQuestion
omdat ze niet nodig zijn: u heeft de variabele gedeclareerd als niet-nullable. De waarde kan niet null
zijn.
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 null-aantekening bevindt, krijgt u waarschuwingen wanneer u null
doorgeeft aan een methode die een niet-null-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:
- Bouw een methode waarmee respondentobjecten worden gegenereerd. Deze vertegenwoordigen mensen die gevraagd worden om de enquête in te vullen.
- 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.
- Herhaal dit totdat voldoende respondenten de enquête hebben beantwoord.
U hebt een klas nodig om een enquêteantwoord weer te geven, dus voeg dat 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:
- Vraag om deelname aan de enquête. Als de persoon geen toestemming geeft, retourneert u een ontbrekend (of null) antwoord.
- 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 null kan zijn. 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
derefereert zonder eerst de waarde van null
te controleren, krijgt u een waarschuwing van de compiler. U krijgt geen waarschuwing in de AnswerSurvey
methode omdat de compiler kan bepalen surveyResponses
variabele is ingesteld op een niet-null-waarde hierboven.
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 alleen 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 null kan zijn. Dit geeft aan dat de enquête nog niet aan respondenten is gegeven. U ziet dat respondenten worden toegevoegd totdat er voldoende toestemming is 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 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 nodig in deze code omdat u de onderliggende interfaces zo hebt ontworpen dat ze allemaal niet-nullbare verwijzingstypen retourneren.
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 null
dereferentie maakt.
Volgende stappen
Meer informatie over het gebruik van een null-verwijzingstype bij het gebruik van Entity Framework: