다음을 통해 공유


자습서: nullable 및 non-nullable 참조 유형을 사용하여 디자인 의도를 보다 명확하게 표현하십시오.

널리 참조 형식은 널리 값 형식이 값 형식을 보완하는 것과 동일한 방식으로 참조 형식을 보완합니다. 형식에 ? 추가하여 변수를 nullable 참조 형식 선언합니다. 예를 들어 string? nullable string나타냅니다. 이러한 새 유형을 사용하여 설계 의도를 보다 명확하게 표현할 수 있습니다. 일부 변수는 항상 값을 가져야 하고, 다른 변수는 값이 누락될 수 있습니다.

이 자습서에서는 다음 방법을 알아봅니다.

  • nullable 및 null이 불가능한 참조 타입을 설계에 도입하세요
  • 코드 전체에서 nullable 참조 형식 검사를 사용하도록 설정합니다.
  • 컴파일러가 이러한 디자인 결정을 적용하는 코드를 작성합니다.
  • 사용자 고유의 디자인에서 nullable 참조 기능 사용

필수 구성 요소

C# 컴파일러를 포함하여 .NET을 실행하도록 컴퓨터를 설정해야 합니다. C# 컴파일러는 Visual Studio 2022또는 .NET SDK사용할 수 있습니다.

이 자습서에서는 Visual Studio 또는 .NET CLI를 포함하여 C# 및 .NET에 익숙하다고 가정합니다.

디자인에 nullable 참조 형식 통합

이 자습서에서는 설문 조사를 실행하는 모델을 제공하는 라이브러리를 빌드합니다. 이 코드는 nullable 참조 형식과 nullable이 아닌 참조 형식을 모두 사용하여 실제 개념을 나타냅니다. 설문 조사 질문은 null일 수 없습니다. 응답자는 질문에 대답하지 않는 것을 선호할 수 있습니다. 이 경우 응답이 null 수 있습니다.

이 샘플에 대해 작성할 코드는 해당 의도를 나타내고 컴파일러는 해당 의도를 적용합니다.

애플리케이션을 만들고 nullable 참조 형식을 사용하도록 설정

dotnet new console사용하여 Visual Studio 또는 명령줄에서 새 콘솔 애플리케이션을 만듭니다. 애플리케이션 이름을 NullableIntroduction으로 정하세요. 애플리케이션을 만든 후에는 전체 프로젝트가 사용하도록 설정된 nullable 주석 컨텍스트에서 컴파일되도록 지정해야. .csproj 파일을 열고 PropertyGroup 요소에 Nullable 요소를 추가합니다. 해당 값을 enable설정합니다. C# 11 이전 프로젝트에서 nullable 참조 형식 기능을 옵트인해야 합니다. 특징이 켜지면 기존의 참조 변수 선언이 널이 허용되지 않는 참조 형식이 되기 때문입니다. 이 결정은 기존 코드에 적절한 null 검사가 없을 수 있는 문제를 찾는 데 도움이 되지만 원래 디자인 의도를 정확하게 반영하지 못할 수 있습니다.

<Nullable>enable</Nullable>

.NET 6 이전에는 새 프로젝트에 Nullable 요소가 포함되지 않습니다. .NET 6부터 새 프로젝트에는 프로젝트 파일의 <Nullable>enable</Nullable> 요소가 포함되었습니다.

애플리케이션의 형식 디자인

이 설문 조사 애플리케이션을 사용하려면 다음과 같은 다양한 클래스를 만들어야 합니다.

  • 질문 목록을 모델로 하는 클래스입니다.
  • 설문 조사를 위해 연락한 사용자 목록을 모델로 하는 클래스입니다.
  • 설문 조사를 수행한 사용자의 답변을 모델로 하는 클래스입니다.

이러한 형식은 nullable 및 non-nullable 참조 형식을 모두 활용하여 필수 멤버와 선택적 멤버를 표현합니다. Nullable 참조 형식은 해당 디자인 의도를 명확하게 전달합니다.

  • 설문 조사의 일부인 질문은 null일 수 없습니다. 빈 질문을 하는 것은 의미가 없습니다.
  • 응답자는 null일 수 없습니다. 연락한 사람, 참여를 거부한 응답자까지 추적하려고 합니다.
  • 질문에 대한 응답은 null일 수 있습니다. 응답자는 일부 또는 모든 질문에 대한 답변을 거부할 수 있습니다.

C#에서 프로그래밍한 경우 null 값을 허용하는 참조 형식에 너무 익숙해서 nullable이 아닌 인스턴스를 선언할 다른 기회를 놓쳤을 수 있습니다.

  • 질문 컬렉션은 null을 허용하지 않아야 합니다.
  • 응답자의 컬렉션은 null을 허용하지 않아야 합니다.

코드를 작성할 때 참조에 대한 기본값으로 null이 아닌 참조 형식을 사용하면 NullReferenceException와 같은 일반적인 실수를 방지할 수 있음을 알게 될 것입니다. 이 자습서의 한 단원은 null수 있거나 사용할 수 없는 변수에 대한 결정을 내렸다는 것입니다. 언어는 이러한 결정을 표현하는 구문을 제공하지 않았습니다. 이제 그렇습니다.

빌드할 앱은 다음 단계를 수행합니다.

  1. 설문 조사를 만들고 질문을 추가합니다.
  2. 설문 조사를 위한 가상 임의 응답자 집합을 생성합니다.
  3. 완료된 설문 조사 크기가 목표 수에 도달할 때까지 응답자에게 연락합니다.
  4. 설문 조사 응답에 대한 중요한 통계를 작성합니다.

nullable 및 non-nullable 참조 형식을 사용하여 설문 조사 빌드

작성할 첫 번째 코드는 설문 조사를 만듭니다. 설문 조사 질문 및 설문 조사 실행을 모델링하는 수업을 작성합니다. 설문 조사에는 답변 형식으로 구분되는 세 가지 유형의 질문( 예/아니요 답변, 숫자 답변 및 텍스트 답변)이 있습니다. public SurveyQuestion 클래스를 만듭니다.

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

컴파일러는 모든 참조 형식 변수 선언을 사용할 수 있는 nullable 주석 컨텍스트의 코드에 대한 nullable이 아닌 참조 형식으로 해석합니다. 다음 코드와 같이 질문 텍스트 및 질문 유형에 대한 속성을 추가하여 첫 번째 경고를 볼 수 있습니다.

namespace NullableIntroduction
{
    public enum QuestionType
    {
        YesNo,
        Number,
        Text
    }

    public class SurveyQuestion
    {
        public string QuestionText { get; }
        public QuestionType TypeOfQuestion { get; }
    }
}

QuestionText초기화하지 않았기 때문에 컴파일러는 nullable이 아닌 속성이 초기화되지 않았다는 경고를 발생합니다. 디자인에서는 질문 텍스트가 null이 아니어야 하므로 생성자를 추가하여 초기화하고 QuestionType 값을 추가해야 합니다. 완성된 클래스 정의는 다음 코드와 같습니다.

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);
}

생성자를 추가하면 경고가 제거됩니다. 생성자 인수는 null을 허용하지 않는 참조 형식이기도 하므로 컴파일러에서 경고를 발생하지 않습니다.

다음으로, SurveyRun라는 public 클래스를 만듭니다. 이 클래스에는 다음 코드와 같이 설문 조사에 질문을 추가하는 SurveyQuestion 개체 및 메서드 목록이 포함되어 있습니다.

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);
    }
}

이전과 마찬가지로 목록 개체를 null이 아닌 값으로 초기화해야 합니다. 그렇지 않으면 컴파일러에서 경고가 발생합니다. AddQuestion의 두 번째 오버로드에는 null 검사가 필요하지 않습니다. 해당 변수를 null이 될 수 없도록 선언했기 때문입니다. 해당 값은 null일 수 없습니다.

편집기에서 Program.cs 전환하고 Main 내용을 다음 코드 줄로 바꿉니다.

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 허용 주석 컨텍스트에 있으므로, null 허용되지 않는 참조 형식을 기대하는 메서드에 null을 전달할 때 경고가 표시됩니다. Main에 다음 줄을 추가하여 시도해 보세요.

surveyRun.AddQuestion(QuestionType.Text, default);

응답자 만들기 및 설문 조사에 대한 답변 가져오기

다음으로 설문 조사에 대한 답변을 생성하는 코드를 작성합니다. 이 프로세스에는 다음과 같은 몇 가지 작은 작업이 포함됩니다.

  1. 응답자 개체를 생성하는 메서드를 빌드합니다. 이들은 설문 조사를 작성하도록 요청받은 사람들을 나타냅니다.
  2. 응답자에게 질문하고 답변을 수집하거나 응답자가 대답하지 않았음을 나타내는 시뮬레이션 논리를 작성합니다.
  3. 충분한 응답자가 설문 조사에 응답할 때까지 반복합니다.

설문 조사 응답을 나타내는 클래스가 필요하므로 지금 추가합니다. nullable 지원을 사용하도록 설정합니다. 다음 코드와 같이 Id 속성 및 속성을 초기화하는 생성자를 추가합니다.

namespace NullableIntroduction
{
    public class SurveyResponse
    {
        public int Id { get; }

        public SurveyResponse(int id) => Id = id;
    }
}

다음으로, 임의의 ID를 생성하여 새 참가자를 만드는 static 메서드를 추가합니다.

private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());

이 클래스의 주요 책임은 설문 조사의 질문에 대한 참가자에 대한 응답을 생성하는 것입니다. 이 책임에는 다음과 같은 몇 가지 단계가 있습니다.

  1. 설문 조사에 참여하도록 요청합니다. 사용자가 동의하지 않으면 누락된(또는 null) 응답을 반환합니다.
  2. 각 질문을 하고 대답을 기록합니다. 각 답변이 누락(또는 null)될 수도 있습니다.

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!";
    }
}

설문 조사 답변의 저장소는 null일 수 있음을 나타내는 Dictionary<int, string>?입니다. 새 언어 기능을 사용하여 컴파일러와 나중에 코드를 읽는 모든 사람에게 디자인 의도를 선언합니다. null 값을 먼저 확인하지 않고 surveyResponses 역참조하는 경우 컴파일러 경고가 표시됩니다. 컴파일러가 위의 null이 아닌 값으로 설정된 surveyResponses 변수를 확인할 수 있으므로 AnswerSurvey 메서드에 경고가 표시되지 않습니다.

누락된 답변에 null 사용하면 null 허용 참조 형식을 사용하는 핵심 요소가 강조 표시됩니다. 목표는 프로그램에서 모든 null 값을 제거하는 것이 아닙니다. 대신, 작성하는 코드가 디자인의 의도를 표현하도록 하는 것이 목표입니다. 누락된 값은 코드에서 표현하는 데 필요한 개념입니다. null 값은 누락된 값을 표현하는 명확한 방법입니다. 모든 null 값을 제거하려고 하면 누락된 값을 null않고 표현하는 다른 방법만 정의할 수 있습니다.

다음으로, SurveyRun 클래스에 PerformSurvey 메서드를 작성해야 합니다. 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);
    }
}

여기서도 nullable List<SurveyResponse>?을 선택하면 응답이 null일 수 있음을 나타냅니다. 이는 설문 조사가 아직 응답자에게 제공되지 않았음을 나타냅니다. 응답자가 충분히 동의할 때까지 계속 추가됩니다.

설문 조사를 실행하는 마지막 단계는 Main 메서드의 끝에서 설문 조사를 수행하는 호출을 추가하는 것입니다.

surveyRun.PerformSurvey(50);

설문 조사 응답 분석

마지막 단계는 설문 조사 결과를 표시하는 것입니다. 작성한 많은 클래스에 코드를 추가합니다. 이 코드는 nullable 참조 형식과 nullable이 아닌 참조 형식을 구분하는 것의 중요성을 보여 줍니다. 먼저 SurveyResponse 클래스에 다음 두 식 본문 멤버를 추가합니다.

public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";

surveyResponses nullable 참조 형식이므로 참조를 해제하기 전에 null 검사가 필요합니다. Answer 메서드는 null을 허용하지 않는 문자열을 반환하므로 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일 수 없다는 점을 고려해야 합니다. ?? 제거하고 뒤에 오는 빈 시퀀스를 제거하여 해당 식을 변경하는 경우 컴파일러는 메서드가 null 반환할 수 있음을 경고하고 반환 서명은 nullable이 아닌 형식을 반환합니다.

마지막으로 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");
    }
}

기본 인터페이스가 모두 nullable이 아닌 참조 형식을 반환하도록 설계했기 때문에 이 코드에서 null 검사가 필요하지 않습니다.

코드 가져오기

csharp/NullableIntroduction 폴더의 샘플 리포지토리에서 완성된 자습서에 대한 코드를 가져올 수 있습니다.

nullable 참조 형식과 nullable이 아닌 참조 형식 간에 형식 선언을 변경하여 실험합니다. null을 실수로 역참조하지 않도록 서로 다른 경고를 생성하는 방법을 확인하십시오.

다음 단계

Entity Framework를 사용할 때 nullable 참조 형식을 사용하는 방법을 알아봅니다.