Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wskazówka
Nie znasz typów referencyjnych dopuszczających wartość null? Najpierw odczytaj typy referencyjne dopuszczane do wartości null . W tym samouczku założono, że rozumiesz różnicę między typami referencyjnymi niedopuszczającymi wartości null a typami referencyjnymi dopuszczającymi wartość null oraz to, jak kompilator śledzi stan null.
Pochodzi z innego języka? Jeśli korzystałeś(-aś) z typów nullable w Kotlinie, strictNullChecks w TypeScripcie lub typów opcjonalnych w Swifcie, ten model pojęciowy jest bezpośrednio analogiczny. W tym ćwiczeniu przedstawiono wyrażanie intencji projektu, a nie uczenie się składni.
W tym samouczku utworzysz małą bibliotekę, która modeluje uruchamianie ankiety. Dane mają dwa odrębne wzorce, które dzięki nullable typom referencyjnym można rozróżnić:
-
Pytanie ankiety musi zawsze być obecne. Lista pytań i tekst każdego pytania nigdy nie może być
null. - Może brakować odpowiedzi na pytanie . Respondenci mogą odrzucić odpowiedź na niektóre lub wszystkie pytania, a model powinien to jawnie określić.
Te reguły deklaruje się za pomocą typów referencyjnych niedopuszczających wartości null i dopuszczających wartość null. Kompilator ostrzega wtedy, gdy zachowanie kodu nie jest zgodne z projektem.
W tym samouczku nauczysz się następujących rzeczy:
- Utwórz aplikację.
- Utwórz pytania dotyczące ankiety.
- Utwórz ankietę z pytaniami.
- Przetestuj wymóg nieprzyjmowania wartości null.
- Kompiluj typy odpowiedzi.
- Utwórz respondentów.
- Wygeneruj jedną odpowiedź na ankietę.
- Utwórz zestaw odpowiedzi na ankietę.
- Sprawdź wyniki ankiety.
Ankietę modelują trzy klasy:
-
SurveyQuestion: jedno pytanie. Wymagany jest tekst i typ pytania. -
SurveyRun: kolekcja pytań oraz lista respondentów. -
SurveyResponse: odpowiedzi jednego respondenta, które mogą brakować.
Każdy typ używa niezerowalnych typów referencyjnych dla wymaganych wartości oraz zerowalnych typów referencyjnych dla brakujących wartości.
Wymagania wstępne
- Najnowszy zestaw SDK .NET
- Edytor Visual Studio Code
- Zestaw deweloperski C#
W tym samouczku założono, że znasz język C# i Visual Studio lub interfejs wiersza polecenia .NET.
Utwórz aplikację i włącz typy odwołań dopuszczających wartość null
Utwórz nową aplikację konsolową o nazwie NullableIntroduction:
dotnet new console -n NullableIntroduction
cd NullableIntroduction
Tworzenie pytań dotyczących ankiety
Dodaj nowy plik o nazwie SurveyQuestion.cs do projektu i zastąp jego zawartość następującym kodem. Tekst i typ pytania nie mogą mieć wartości null, więc konstruktor musi zainicjować oba:
namespace NullableIntroduction;
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion(QuestionType typeOfQuestion, string text)
{
public string QuestionText { get; } = text;
public QuestionType TypeOfQuestion { get; } = typeOfQuestion;
}
Parametry konstruktora są typami odwołania bez wartości null, więc kompilator ostrzega obiekt wywołujący, jeśli którykolwiek z argumentów może mieć wartość null.
Tworzenie ankiety dotyczącej pytań
Następnie dodaj nowy plik o nazwie SurveyRun.cs do projektu i zdefiniuj klasę SurveyRun do przechowywania listy pytań:
namespace NullableIntroduction;
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = [];
public void AddQuestion(QuestionType type, string question) =>
AddQuestion(new SurveyQuestion(type, question));
public void AddQuestion(SurveyQuestion surveyQuestion) =>
surveyQuestions.Add(surveyQuestion);
}
Pole surveyQuestions jest polem typu List<SurveyQuestion>, które nie przyjmuje wartości null. Używa wyrażenia kolekcji do inicjowania pustej listy. Oba AddQuestion przeciążenia akceptują parametry niezwiązane z wartościami null, więc kompilator wymusza, że obiekty wywołujące nie przekazują nullparametrów .
W Program.cspliku utwórz element SurveyRun i dodaj trzy pytania:
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?");
Testowanie wymagania not-null
Aby zobaczyć, jak kompilator wymusza, by parametry nie mogły mieć wartości null, spróbuj dodać następujący wiersz i ponownie przebudować projekt:
surveyRun.AddQuestion(QuestionType.Text, default);
Kompilator zgłasza ostrzeżenie CS8625, ponieważ default ma wartość null dla typu referencyjnego, a AddQuestion oczekuje niezerowalnego string. Usuń wiersz przed kontynuowaniem.
Tworzenie typów odpowiedzi
Respondenci mogą odrzucić ankietę, a nawet jeśli uczestniczą, mogą pominąć poszczególne pytania. Obie formy "brakujące" są prawidłowymi wynikami, a system typów powinien je uwidocznić. Obie formy zapisuje się za pomocą null.
Dodaj nowy plik o nazwie SurveyResponse.cs do projektu i zdefiniuj klasę SurveyResponse . Użyj konstruktora głównego (parametry zadeklarowane bezpośrednio w typie, dostępne w całym jego ciele), aby przekazać zawsze wymagane Id:
namespace NullableIntroduction;
public class SurveyResponse(int id)
{
public int Id { get; } = id;
}
Tworzenie respondentów
Dodaj statyczną metodę wytwórczą (static metodę, która tworzy i zwraca nową instancję typu, alternatywę dla bezpośredniego wywołania konstruktora), która tworzy obiekty respondentów z losowym identyfikatorem:
private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());
Generuj jedną odpowiedź na ankietę
Następnie dodaj metodę, która zadaje ankietę respondentowi. Przechowuj odpowiedzi w słowniku dopuszczającym wartość null, aby sam typ wskazywał, że respondent może odmówić odpowiedzi:
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!";
}
}
Pole surveyResponses jest Dictionary<int, string>?. Jeśli wykonasz dereferencję pola bez uprzedniego sprawdzenia null, kompilator wyświetli ostrzeżenie. Wewnątrz AnswerSurvey kompilator śledzi, że surveyResponsesnie jest nullem bezpośrednio po wyrażeniu new, więc ciało pętli nie wymaga dodatkowego sprawdzenia.
Tworzenie zestawu odpowiedzi na ankietę
Dodaj metodę SurveyRun , która tworzy listę respondentów do momentu wystarczającej zgody na uczestnictwo:
private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
int respondentsConsenting = 0;
respondents = [];
while (respondentsConsenting < numberOfRespondents)
{
var respondent = SurveyResponse.GetRandomId();
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
}
}
Pole respondents ma wartość List<SurveyResponse>? — pozostaje null do czasu uruchomienia ankiety.
Wywołaj PerformSurvey z Main:
surveyRun.PerformSurvey(50);
Badanie wyników ankiety
Aby zgłaszać wyniki, udostępnij kilka funkcji pomocniczych z SurveyResponse i SurveyRun. W elemencie SurveyResponse dodaj składowe z treścią wyrażeniową (składowe definiowane za pomocą => i pojedynczego wyrażenia zamiast bloku { ... }), które obsługują słownik dopuszczający wartość null:
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
AnsweredSurvey sprawdza pole względem null.
Answer używa ?. operatora warunkowego null (który daje w wyniku null, gdy lewa strona ma wartość null, zamiast zgłaszać wyjątek), aby bezpiecznie wykonać dereferencję, oraz ?? operatora łączenia wartości null (który podstawia prawy operand, gdy lewy ma wartość null), aby zapewnić wartość zastępczą różną od null. Typ zwracany przez metodę nie dopuszcza wartości null string, więc kod wywołujący nie musi sprawdzać, czy wartość jest równa null.
W SurveyRun dodaj składowe o składni wyrażeniowej, które udostępniają listę uczestników i pytań:
public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];
AllParticipants zwraca sekwencję nienullowalną, mimo że respondents może mieć wartość null. Operator ?? zastępuje Enumerable.Empty<SurveyResponse>() pole, gdy pole nie zostało jeszcze wypełnione. Jeśli usuniesz klauzulę ??, kompilator ostrzega, że metoda może zwrócić null, mimo że typ zwracany nie dopuszcza wartości null.
Na koniec napisz raport w dolnej części elementu 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");
}
}
Zwróć uwagę, że nie trzeba sprawdzać wartości null dla participant, surveyRun.Questions ani surveyRun.GetQuestion(i). Typy deklarują te wartości jako niepuste, więc kompilator traktuje je jako nie null w całej pętli.
Uruchom aplikację:
dotnet run
Wynik jest inny przy każdym uruchomieniu, ponieważ respondenci są generowani losowo, ale każdy wiersz zawiera odpowiedzi uczestnika albo informację, że odmówił on odpowiedzi.
Podsumowanie
Gotowy przykład znajduje się w folderze csharp/NullableIntroduction repozytorium dotnet/samples . Poeksperymentuj, zmieniając typy między dopuszczanymi wartościami null i niepustymi. Usunięcie elementu ? w sytuacji, gdy projekt dopuszcza brakujące wartości, powoduje ostrzeżenia kompilatora, które wskazują każde miejsce, w którym brak wartości ma znaczenie.