Självstudie: Uttrycka designavsikten tydligare med nullbara och icke-nullbara referenstyper
Nullbara referenstyper kompletterar referenstyper på samma sätt som nullbara värdetyper kompletterar värdetyper. Du deklarerar en variabel som en nullbar referenstyp genom att lägga till en ?
till typen.
string?
representerar till exempel en nullbar string
. Du kan använda dessa nya typer för att tydligare uttrycka din design avsikt: vissa variabler måste alltid ha ett värde, andra kanske saknar ett värde.
I den här självstudien får du lära dig att:
- Införliva null- och icke-nullbara referenstyper i dina design
- Aktivera kontroller av nullbara referenstyper genom hela din kod.
- Skriv kod där kompilatorn tillämpar dessa designbeslut.
- Använd den nullbara referensfunktionen i dina egna design
Förutsättningar
Du måste konfigurera datorn för att köra .NET, inklusive C#-kompilatorn. C#-kompilatorn är tillgänglig med Visual Studio 2022eller .NET SDK.
Den här handledningen förutsätter att du är bekant med C# och .NET, inklusive Visual Studio eller .NET CLI.
Införliva nullbara referenstyper i dina designer
I den här handledningen skapar du ett bibliotek som modellerar genomförandet av en undersökning. Koden använder både nullbara referenstyper och icke-nullbara referenstyper för att representera de verkliga begreppen. Enkätfrågorna kan aldrig vara null. En svarande kanske föredrar att inte svara på en fråga. Svaren kan vara null
i det här fallet.
Koden du skriver för det här exemplet uttrycker den avsikten, och kompilatorn framtvingar den avsikten.
Skapa programmet och aktivera null-referenstyper
Skapa ett nytt konsolprogram antingen i Visual Studio eller från kommandoraden med hjälp av dotnet new console
. Ge programmet namnet NullableIntroduction
. När du har skapat programmet måste du ange att hela projektet kompileras i en aktiverad nullable anteckningskontext. Öppna filen .csproj och lägg till ett Nullable
-element i elementet PropertyGroup
. Ange värdet till enable
. Du måste välja null-referenstyper funktion i projekt som är tidigare än C# 11. Det beror på att när funktionen är aktiverad blir befintliga referensvariabeldeklarationer icke-nullbara referenstyper. Även om det beslutet hjälper dig att hitta problem där befintlig kod kanske inte har rätt nullkontroller, kanske det inte korrekt återspeglar din ursprungliga designavsikt.
<Nullable>enable</Nullable>
Före .NET 6 innehåller nya projekt inte elementet Nullable
. Från och med .NET 6 innehåller nya projekt elementet <Nullable>enable</Nullable>
i projektfilen.
Utforma typerna för programmet
Det här undersökningsprogrammet kräver att du skapar ett antal klasser:
- En klass som modellerar listan med frågor.
- En klass som modellerar en lista över personer som har kontaktats för undersökningen.
- En klass som modellerar svaren från en person som gjorde undersökningen.
Dessa typer använder både null- och icke-nullbara referenstyper för att uttrycka vilka medlemmar som krävs och vilka medlemmar som är valfria. Referenstyper som kan vara null indikerar designavsikten tydligt.
- De frågor som ingår i undersökningen kan aldrig vara null: Det är ingen mening att ställa en tom fråga.
- Respondenterna kan aldrig vara null. Du vill spåra personer som du har kontaktat, även de svarande som avböjt att delta.
- Alla svar på en fråga kan vara null. Respondenter kan avböja att svara på några eller alla frågor.
Om du har programmerat i C# kanske du är så van vid referenstyper som tillåter null
värden att du kanske har missat andra möjligheter att deklarera icke-nullbara instanser:
- Frågesamlingen bör inte vara nullbar.
- Insamlingen av respondenter bör inte vara nullbar.
När du skriver koden ser du att en referenstyp som inte är nullbar som standard för referenser undviker vanliga misstag som kan leda till NullReferenceExceptions. En lärdom av den här självstudien är att du fattade beslut om vilka variabler som kunde eller inte kunde null
. Språket angav inte syntax för att uttrycka dessa beslut. Nu gör det det.
Appen som du skapar utför följande steg:
- Skapar en undersökning och lägger till frågor till den.
- Skapar en pseudo-slumpmässig uppsättning respondenter för undersökningen.
- Kontakta respondenterna tills den slutförda undersökningsstorleken når målnumret.
- Skriver ut viktig statistik om enkätsvaren.
Skapa undersökningen med null- och icke-nullbara referenstyper
Den första koden du skriver skapar undersökningen. Du skriver klasser för att modellera en undersökningsfråga och en undersökningsexekvering. Din undersökning har tre typer av frågor, som skiljer sig från svarets format: Ja/Nej-svar, nummersvar och textsvar. Skapa en public SurveyQuestion
-klass:
namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}
Kompilatorn tolkar varje variabeldeklaration av referenstyp som en icke-nullbar referenstyp för kod i en aktiverad ogiltig anteckningskontext. Du kan se din första varning genom att lägga till egenskaper för frågetexten och typen av fråga, enligt följande kod:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion
{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
}
}
Eftersom du inte har initierat QuestionText
utfärdar kompilatorn en varning om att en egenskap som inte kan nulliseras inte har initierats. Din design kräver att frågetexten inte är null, så du lägger till en konstruktor för att initiera den och QuestionType
värdet också. Den färdiga klassdefinitionen ser ut som följande kod:
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);
}
När du lägger till konstruktorn tar du bort varningen. Konstruktorargumentet är också en referenstyp som inte kan nulliseras, så kompilatorn utfärdar inga varningar.
Skapa sedan en public
-klass med namnet SurveyRun
. Den här klassen innehåller en lista över SurveyQuestion
objekt och metoder för att lägga till frågor i undersökningen, enligt följande kod:
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);
}
}
Precis som tidigare måste du initiera listobjektet till ett värde som inte är null eller så utfärdar kompilatorn en varning. Det finns inga null-kontroller i den andra överlagringen av AddQuestion
eftersom de inte behövs: Du har deklarerat att variabeln inte är nullbar. Dess värde kan inte vara null
.
Växla till Program.cs i redigeringsprogrammet och ersätt innehållet i Main
med följande kodrader:
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?");
Eftersom hela projektet är i en aktiverad nullbar anteckningskontext får du varningar när du skickar null
till alla metoder som förväntar sig en referenstyp som inte kan ogiltigförklaras. Prova genom att lägga till följande rad i Main
:
surveyRun.AddQuestion(QuestionType.Text, default);
Skapa respondenter och få svar på undersökningen
Skriv sedan koden som genererar svar på undersökningen. Den här processen omfattar flera små uppgifter:
- Skapa en metod som genererar respondentobjekt. Dessa representerar personer som ombetts att fylla i undersökningen.
- Skapa logik för att simulera att ställa frågor till en svarande och samla in svar eller notera att en svarande inte svarade.
- Upprepa tills tillräckligt många svarande har besvarat undersökningen.
Du behöver en klass för att representera ett undersökningssvar, så lägg till det nu. Aktivera stöd som kan ogiltigförklaras. Lägg till en Id
-egenskap och en konstruktor som initierar den, enligt följande kod:
namespace NullableIntroduction
{
public class SurveyResponse
{
public int Id { get; }
public SurveyResponse(int id) => Id = id;
}
}
Lägg sedan till en static
metod för att skapa nya deltagare genom att generera ett slumpmässigt ID:
private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());
Huvudansvaret för den här klassen är att generera svar för en deltagare på frågorna i undersökningen. Det här ansvaret har några steg:
- Be om deltagande i undersökningen. Om personen inte samtycker, returnera ett bristande (eller null)-svar.
- Ställ varje fråga och spela in svaret. Varje svar kan också saknas (eller null).
Lägg till följande kod i klassen SurveyResponse
:
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!";
}
}
Lagringen för undersökningssvaren är Dictionary<int, string>?
, vilket anger att den kan vara null. Du använder den nya språkfunktionen för att deklarera din designavsikt, både till kompilatorn och till alla som läser koden senare. Om du någonsin derefererar surveyResponses
utan att först kontrollera null
-värdet, får du en kompilatorvarning. Du får ingen varning i metoden AnswerSurvey
eftersom kompilatorn kan fastställa surveyResponses
variabeln har angetts till ett värde som inte är null ovan.
Om du använder null
för saknade svar markeras en nyckelpunkt för att arbeta med null-referenstyper: målet är inte att ta bort alla null
värden från programmet. Målet är i stället att se till att den kod du skriver uttrycker avsikten med din design. Saknade värden är ett nödvändigt begrepp för att uttrycka i koden. Värdet null
är ett tydligt sätt att uttrycka de saknade värdena. Att försöka ta bort alla null
värden leder bara till att definiera något annat sätt att uttrycka de saknade värdena utan null
.
Därefter måste du skriva metoden PerformSurvey
i klassen SurveyRun
. Lägg till följande kod i klassen 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);
}
}
Här igen anger ditt val av en nullbar List<SurveyResponse>?
att svaret kan vara null. Det indikerar att undersökningen inte har getts till några respondenter ännu. Observera att respondenterna läggs till tills tillräckligt många har samtyckt.
Det sista steget för att köra undersökningen är att lägga till ett anrop för att utföra undersökningen i slutet av Main
-metoden:
surveyRun.PerformSurvey(50);
Granska enkätsvar
Det sista steget är att visa undersökningsresultat. Du lägger till kod i många av de klasser som du har skrivit. Den här koden visar värdet för att särskilja null- och icke-nullbara referenstyper. Börja med att lägga till följande två uttrycksbaserade medlemmar i klassen SurveyResponse
:
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
Eftersom surveyResponses
är en nullbara referenstyp är null-kontroller nödvändiga innan den avrefereras. Metoden Answer
returnerar en icke-nullbar sträng, så vi måste hantera fallet med ett svar som saknas med hjälp av null-sammanfogande operatorn.
Lägg sedan till dessa tre uttrycksbaserade medlemmar i klassen SurveyRun
:
public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];
Den AllParticipants
medlemmen måste ta hänsyn till att respondents
variabeln kan vara null, men returvärdet kan inte vara null. Om du ändrar uttrycket genom att ta bort ??
och den tomma sekvensen som följer varnar kompilatorn dig om att metoden kan returnera null
och dess retursignatur returnerar en typ som inte går att nullera.
Lägg slutligen till följande loop längst ned i metoden 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");
}
}
Du behöver inga null
kontroller i den här koden eftersom du har utformat de underliggande gränssnitten så att alla returnerar referenstyper som inte går att nolla.
Hämta koden
Du kan hämta koden för den färdiga självstudien från vårt exempelarkiv i mappen csharp/NullableIntroduction.
Experimentera genom att ändra typdeklarationerna mellan null- och icke-nullbara referenstyper. Se hur det genererar olika varningar för att säkerställa att du inte av misstag avrefererar en null
.
Nästa steg
Lär dig hur du använder nullbar referenstyp när du använder Entity Framework: