Tip
nullable 참조 형식이 새로운가요? 먼저 Nullable 참조 형식을 읽습니다. 이 자습서에서는 null 비허용 참조 형식과 null 허용 참조 형식의 차이점, 그리고 컴파일러가 null 상태를 추적하는 방식을 이해하고 있다고 가정합니다.
다른 언어에서 오시겠습니까? Kotlin의 nullable 형식, TypeScript strictNullChecks또는 Swift의 선택 사항을 사용한 경우 개념 모델이 직접 매핑됩니다. 여기서 연습은 구문을 학습 하지 않고 디자인 의도를 표현하는 것입니다.
이 자습서에서는 설문 조사를 실행하는 모델을 제공하는 작은 라이브러리를 빌드합니다. 데이터에는 nullable 참조 형식을 사용하여 구분할 수 있는 두 가지 고유한 패턴이 있습니다.
-
설문 조사 질문은 항상 존재해야 합니다. 질문 목록과 각 질문의 텍스트는 절대로 사용할
null수 없습니다. - 질문에 대한 응답이 누락되었을 수 있습니다. 응답자는 일부 또는 모든 질문에 대한 답변을 거부할 수 있으며 모델은 이를 명시적으로 만들어야 합니다.
해당 규칙은 null 허용 참조 형식과 null 비허용 참조 형식으로 선언합니다. 그런 다음, 컴파일러는 코드의 동작이 디자인과 일치하지 않을 때마다 경고합니다.
이 자습서에서는 다음을 수행합니다.
- 애플리케이션을 만듭니다.
- 설문 조사 질문을 작성합니다.
- 질문에 대한 설문 조사를 작성합니다.
- null이 아닌 요구 사항을 테스트합니다.
- 응답 형식을 빌드합니다.
- 응답자를 만듭니다.
- 하나의 설문 조사 응답을 생성합니다.
- 설문 조사 응답 집합을 작성합니다.
- 설문 조사 결과를 검토합니다.
세 가지 클래스는 설문 조사를 모델링합니다.
-
SurveyQuestion: 한 가지 질문. 텍스트 및 질문 유형이 필요합니다. -
SurveyRun: 질문 컬렉션과 응답자 목록입니다. -
SurveyResponse: 한 응답자의 답변이 누락되었을 수 있습니다.
각 형식은 필수 값에는 null 비허용 참조 형식을 사용하고, 누락된 값에는 null 허용 참조 형식을 사용합니다.
사전 요구 사항
- 최신 .NET SDK
- Visual Studio Code 편집기
- C# 개발 키트
이 자습서에서는 C# 및 Visual Studio 또는 .NET CLI에 대해 잘 알고 있다고 가정합니다.
애플리케이션을 만들고 nullable 참조 형식을 사용하도록 설정
다음과 같은 새 콘솔 애플리케이션을 만듭니다 NullableIntroduction.
dotnet new console -n NullableIntroduction
cd NullableIntroduction
설문 조사 질문 작성
프로젝트에 명명된 SurveyQuestion.cs 새 파일을 추가하고 해당 내용을 다음 코드로 바꿉니다. 텍스트와 질문 형식은 null을 허용하지 않으므로 생성자는 다음을 모두 초기화해야 합니다.
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;
}
생성자 매개 변수는 nullable이 아닌 참조 형식이므로 두 인수 중 하나가 될 수 null있으면 컴파일러가 호출자에게 경고합니다.
질문 설문 조사 작성
다음으로 프로젝트에 명명된 SurveyRun.cs 새 파일을 추가하고 질문 목록을 저장할 클래스를 정의 SurveyRun 합니다.
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);
}
surveyQuestions 필드는 null을 허용하지 않습니다List<SurveyQuestion>.
컬렉션 식을 사용하여 빈 목록을 초기화합니다. 두 AddQuestion 오버로드 모두 null을 허용하지 않는 매개 변수를 받으므로, 컴파일러는 호출자가 null을 전달하지 못하도록 강제합니다.
Program.cs에서 SurveyRun을 만들고 질문 세 개를 추가합니다:
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?");
null이 아닌 요구 사항 테스트
컴파일러가 nullable이 아닌 매개 변수를 적용하는 방법을 확인하려면 다음 줄을 추가하고 다시 빌드합니다.
surveyRun.AddQuestion(QuestionType.Text, default);
컴파일러는 default이(가) 참조 형식에 대해 null로 평가되고 AddQuestion은(는) nullable이 아닌 string을(를) 예상하기 때문에 경고 CS8625를 발생시킵니다. 계속하기 전에 줄을 제거합니다.
응답 유형 만들기
응답자는 설문 조사를 거부할 수 있으며, 참여하더라도 개별 질문을 건너뛸 수 있습니다. 두 가지 형식의 "누락"은 모두 유효한 결과이며 형식 시스템에서 표시되도록 해야 합니다. 을 null사용하여 두 양식을 모두 표현합니다.
프로젝트에 명명된 SurveyResponse.cs 새 파일을 추가하고 클래스를 정의합니다 SurveyResponse .
기본 생성자(형식 자체에 선언된 매개 변수, 본문 전체에서 사용 가능)를 사용하여 항상 필요한 Id다음을 캡처합니다.
namespace NullableIntroduction;
public class SurveyResponse(int id)
{
public int Id { get; } = id;
}
응답자 만들기
임의 ID를 사용하여 응답자를 만드는 정적 팩터리 메서드 ( static 형식의 새 인스턴스를 만들고 반환하는 메서드, 생성자를 직접 호출하는 대신)를 추가합니다.
private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());
하나의 설문 조사 응답 생성
다음으로, 설문 조사를 요청하는 메서드를 응답자에게 추가합니다. 형식 자체만으로도 응답자가 응답을 거부할 수 있음을 나타내도록 답변을 null을 허용하는 사전에 저장합니다.
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!";
}
}
surveyResponses 필드는 Dictionary<int, string>?입니다. 먼저 null를 확인하지 않고 필드를 역참조하면 컴파일러가 경고를 표시합니다.
AnswerSurvey 내부에서 컴파일러는 new 식 직후에 surveyResponses가 null이 아님을 추적하므로 루프 본문에는 추가 검사가 필요하지 않습니다.
설문 조사 응답 집합 작성
참여하기에 충분한 동의가 있을 때까지 응답자 목록을 작성하는 방법을 SurveyRun 추가합니다.
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);
}
}
respondents 필드는 List<SurveyResponse>? 상태입니다. 설문 조사가 실행되기 전까지는 null입니다.
PerformSurvey에서 Main를 호출합니다.
surveyRun.PerformSurvey(50);
설문 조사 결과 검사
결과를 보고하려면 SurveyResponse 및 SurveyRun에서 몇 가지 헬퍼 함수를 노출하세요.
SurveyResponse에 nullable 딕셔너리를 처리하는 식 본문 멤버({ ... } 블록 대신 =>와 단일 식으로 정의된 멤버)를 추가합니다.
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
AnsweredSurvey는 필드를 null와 대조합니다.
Answer에서는 ?. null 조건부 연산자(왼쪽이 null일 때 예외를 발생시키는 대신 null로 평가됨)를 사용하여 안전하게 역참조하고, ?? null 병합 연산자(왼쪽이 null일 때 오른쪽 피연산자로 대체됨)를 사용하여 null이 아닌 대체 값을 제공합니다. 메서드의 반환 형식은 null을 허용하지 string않으므로 호출자는 null 검사가 필요하지 않습니다.
SurveyRun에 참가자 및 질문 목록을 노출하는 식 본문 멤버를 추가합니다:
public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];
AllParticipants는 respondents이 null일 수 있음에도 불구하고 null을 허용하지 않는 시퀀스를 반환합니다.
?? 필드가 Enumerable.Empty<SurveyResponse>() 아직 채워지지 않은 경우 연산자가 대체합니다.
?? 절을 제거하면 반환 형식이 null을 허용하지 않는데도 메서드가 null을 반환할 수 있다고 컴파일러가 경고합니다.
마지막으로 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");
}
}
participant, surveyRun.Questions 또는 surveyRun.GetQuestion(i)에 대해서는 null 확인이 필요하지 않다는 점에 유의하세요. 형식은 해당 값을 nullable이 아닌 값으로 선언하므로 컴파일러는 루프 전체에서 null이 아닌 값으로 처리합니다.
애플리케이션을 실행합니다.
dotnet run
응답자가 임의로 생성되기 때문에 각 실행에서 출력이 다르지만 모든 줄은 참가자의 답변 또는 거절한 메모를 보고합니다.
결론
완성된 샘플은 dotnet/samples 리포지토리의 csharp/NullableIntroduction 폴더에 있습니다. nullable과 nullable이 아닌 형식 간에 형식을 변경하여 실험합니다. 디자인에서 ? 누락된 값을 허용하는 위치를 제거하면 누락된 값이 중요한 모든 위치를 가리키는 컴파일러 경고가 생성됩니다.
관련 콘텐츠
.NET