チュートリアル: null 許容参照型と null 非許容参照型を使用して設計意図をもっと明確に示すTutorial: Express your design intent more clearly with nullable and non-nullable reference types

C# 8.0 には null 許容参照型が導入されています。これは、null 許容値型が値型を補完するのと同じように、参照型を補完するものです。C# 8.0 introduces nullable reference types, which complement reference types the same way nullable value types complement value types. 型に ? を追加することで、変数が null 許容参照型であることを宣言します。You declare a variable to be a nullable reference type by appending a ? to the type. たとえば、string? は、null が許容される string を表します。For example, string? represents a nullable string. これらの新しい型を使用して、一部の変数では常に値を持つ必要があり、他の変数では値が欠落することも可能であるという設計意図をさらに明確に示すことができます。You can use these new types to more clearly express your design intent: some variables must always have a value, others may be missing a value.

このチュートリアルでは、次の作業を行う方法について説明します。In this tutorial, you'll learn how to:

  • null 許容参照型と null 非許容参照型を設計に組み込む。Incorporate nullable and non-nullable reference types into your designs
  • コード全体で null 許容参照型をチェックできるようにする。Enable nullable reference type checks throughout your code.
  • コンパイラでこれらの設計上の決定が適用されるコードを記述する。Write code where the compiler enforces those design decisions.
  • 自分の設計の中で null 許容参照機能を使用する。Use the nullable reference feature in your own designs

必須コンポーネントPrerequisites

お使いのコンピューターを、.NET Core が実行されるように設定する必要があります。C# 8.0 コンパイラも実行されるようにします。You'll need to set up your machine to run .NET Core, including the C# 8.0 compiler. C# 8.0 コンパイラは、Visual Studio 2019 または .NET Core 3.0 で使用できます。The C# 8.0 compiler is available with Visual Studio 2019, or .NET Core 3.0.

このチュートリアルでは、.NET と、C# と Visual Studio または .NET Core CLI のいずれかに精通していることを前提としています。This tutorial assumes you're familiar with C# and .NET, including either Visual Studio or the .NET Core CLI.

null 許容参照型と null 非許容参照型を設計に組み込むIncorporate nullable reference types into your designs

このチュートリアルでは、アンケートの実行をモデル化するライブラリを構築します。In this tutorial, you'll build a library that models running a survey. コードでは、null 許容参照型と null 非許容参照型の両方を使用して、現実世界の概念を表します。The code uses both nullable reference types and non-nullable reference types to represent the real-world concepts. アンケートの質問は null することはできません。The survey questions can never be null. 回答者が質問に答えたくない場合があります。A respondent might prefer not to answer a question. この場合、応答は null になる可能性があります。The responses might be null in this case.

このサンプルで記述するコードはそのような意図を表現し、コンパイラにその意図が適用されます。The code you'll write for this sample expresses that intent, and the compiler enforces that intent.

アプリケーションを作成し、null 許容参照型を有効にするCreate the application and enable nullable reference types

新しいコンソール アプリケーションを作成します。Visual Studio を使用するか、コマンド ラインで dotnet new console を使用します。Create a new console application either in Visual Studio or from the command line using dotnet new console. アプリケーションに NullableIntroduction という名前を付けます。Name the application NullableIntroduction. アプリケーションを作成したら、プロジェクト全体が、有効な null 許容注釈コンテキストでコンパイルされるように指定する必要があります。Once you've created the application, you'll need to specify that the entire project compiles in an enabled nullable annotation context. .csproj ファイルを開き、Nullable 要素を PropertyGroup 要素に追加します。Open the .csproj file and add a Nullable element to the PropertyGroup element. 値を enableに設定します。Set its value to enable. C# 8.0 プロジェクトであっても、null 許容参照型機能を選択する必要があります。You must opt into the nullable reference types feature, even in C# 8.0 projects. これは、機能をオンにすると、既存の参照変数宣言が null 非許容参照型になるためです。That's because once the feature is turned on, existing reference variable declarations become non-nullable reference types. その決定は既存のコードで適切な null チェックが行われていない場合に問題を発見するのに役立ちますが、元の設計意図が正確に反映されない可能性があります。While that decision will help find issues where existing code may not have proper null-checks, it may not accurately reflect your original design intent:

<Nullable>enable</Nullable>

アプリケーション用の型を設計するDesign the types for the application

このアンケート アプリケーションでは、いくつかのクラスを作成する必要があります。This survey application requires creating a number of classes:

  • 質問の一覧をモデル化するクラス。A class that models the list of questions.
  • アンケートに応じてもらうために連絡した人物の一覧をモデル化するクラス。A class that models a list of people contacted for the survey.
  • アンケートに応じた人物から得た回答をモデル化するクラス。A class that models the answers from a person that took the survey.

これらの型では、null 許容参照型と null 非許容参照型の両方を利用して、必要なメンバーと省略可能なメンバーを表現します。These types will make use of both nullable and non-nullable reference types to express which members are required and which members are optional. null 許容参照型により、次の設計意図が明確に伝わります。Nullable reference types communicate that design intent clearly:

  • アンケートの一部である質問を null にすることはできません。空の質問は意味がありません。The questions that are part of the survey can never be null: It makes no sense to ask an empty question.
  • 回答者を null にすることはできません。The respondents can never be null. 回答者が参加を辞退している場合でも、連絡した人物を追跡したいからです。You'll want to track people you contacted, even respondents that declined to participate.
  • 質問に対する回答を null にすることができます。Any response to a question may be null. 回答者は、一部の質問またはすべての質問の回答を拒否できます。Respondents can decline to answer some or all questions.

これまで C# でプログラミングしている場合は、null 値を許容する参照型に慣れているため、null を許容しないインスタンスを宣言する機会がなかったかもしれません。If you've programmed in C#, you may be so accustomed to reference types that allow null values that you may have missed other opportunities to declare non-nullable instances:

  • 質問のコレクションは null を許容しないものにする必要があります。The collection of questions should be non-nullable.
  • 回答者のコレクションは null を許容しないものにする必要があります。The collection of respondents should be non-nullable.

コードを記述していくにつれて、参照の既定値としての null 非許容参照型によって、NullReferenceException を引き起こす可能性がある一般的なミスを回避できることを理解できるでしょう。As you write the code, you'll see that a non-nullable reference type as the default for references avoids common mistakes that could lead to NullReferenceExceptions. このチュートリアルから学ぶことの 1 つは、null にすることができる変数とできない変数を決定することです。One lesson from this tutorial is that you made decisions about which variables could or could not be null. この言語には、このような決定を表現するための構文が提供されていませんでした。The language didn't provide syntax to express those decisions. 今、それが実現しています。Now it does.

ビルドするアプリで、次の手順を行います。The app you'll build does the following steps:

  1. アンケートを作成し、そこに質問を追加します。Creates a survey and adds questions to it.
  2. アンケートの回答者の擬似乱数セットを作成します。Creates a pseudo-random set of respondents for the survey.
  3. 回答されたアンケートのサイズが目標数に達するまで、回答者に連絡します。Contacts respondents until the completed survey size reaches the goal number.
  4. アンケートの回答に関する重要な統計情報を書き込みます。Writes out important statistics on the survey responses.

null 許容型と null 非許容型を含むアンケートを作成するBuild the survey with nullable and non-nullable types

最初に記述するコードによって、アンケートが作成されます。The first code you'll write creates the survey. アンケートの質問とアンケートの実行をモデル化するクラスを記述します。You'll write classes to model a survey question and a survey run. アンケートには、回答の形式によって区別される 3 種類の質問があります:はい/いいえで回答するもの、番号で回答するもの、およびテキストで回答するもの。Your survey has three types of questions, distinguished by the format of the answer: Yes/No answers, number answers, and text answers. public SurveyQuestion クラスを作成します。Create a public SurveyQuestion class:

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

コンパイラでは、有効な null 許容注釈コンテキスト内のコードについては、すべての参照型変数の宣言が null 非許容参照型として解釈されます。The compiler interprets every reference type variable declaration as a non-nullable reference type for code in an enabled nullable annotation context. 次のコードに示すように、質問のテキストと質問の種類のプロパティを追加することで、最初の警告を確認できます。You can see your first warning by adding properties for the question text and the type of question, as shown in the following code:

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

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

QuestionText を初期化していないため、コンパイラによって、null を許容しないプロパティが初期化されていないことを示す警告が発行されます。Because you haven't initialized QuestionText, the compiler issues a warning that a non-nullable property hasn't been initialized. この設計では、質問のテキストを null 以外にする必要があるため、初期化するためのコンストラクターを追加します。QuestionType 値も同様にします。Your design requires the question text to be non-null, so you add a constructor to initialize it and the QuestionType value as well. 完成したクラス定義は、次のコードのようになります。The finished class definition looks like the following 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);
    }
}

コンストラクターを追加すると、警告が解除されます。Adding the constructor removes the warning. コンストラクターの引数も、null 非許容参照型であるため、コンパイラによる警告は発行されません。The constructor argument is also a non-nullable reference type, so the compiler doesn't issue any warnings.

次に、SurveyRun という名前の public クラスを作成します。Next, create a public class named SurveyRun. 次のコードに示すように、このクラスには、アンケートに質問を追加する SurveyQuestion オブジェクトとメソッドの一覧が含まれます。This class contains a list of SurveyQuestion objects and methods to add questions to the survey, as shown in the following 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);
    }
}

前と同じように、この一覧オブジェクトを null 以外の値に初期化する必要があります。そうしないとコンパイラによって警告が発行されます。As before, you must initialize the list object to a non-null value or the compiler issues a warning. AddQuestion の 2 つ目のオーバーロードでは null のチェックは必要ないため、それが実行されることはありません。その変数は null 非許容であることが宣言されています。There are no null checks in the second overload of AddQuestion because they aren't needed: You've declared that variable to be non-nullable. その値は null になることはできません。Its value can't be null.

お使いのエディターで Program.cs に切り替え、Main の内容を次のコード行に置き換えます。Switch to Program.cs in your editor and replace the contents of Main with the following lines of 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?");

プロジェクト全体が、有効な null 許容注釈コンテキスト内にあるので、null 非許容参照型が必要なメソッドに null を渡すと、警告が発生します。Because the entire project is in an enabled nullable annotation context, you'll get warnings when you pass null to any method expecting a non-nullable reference type. 次の行を Main に追加して試してください。Try it by adding the following line to Main:

surveyRun.AddQuestion(QuestionType.Text, default);

回答者を作成し、アンケートに対する回答を取得するCreate respondents and get answers to the survey

次に、アンケートに対する回答を生成するコードを記述します。Next, write the code that generates answers to the survey. このプロセスには、いくつかの小さいタスクが含まれます。This process involves several small tasks:

  1. 回答者オブジェクトを生成するメソッドを作成します。Build a method that generates respondent objects. これらは、アンケートへの入力を求められる人物を表します。These represent people asked to fill out the survey.
  2. 回答者にアンケートを依頼し、回答を収集するか、回答者が回答しなかったことを示すデータを収集することをシミュレートするロジックを構築します。Build logic to simulate asking the questions to a respondent and collecting answers or noting that a respondent didn't answer.
  3. 十分な数の回答者がアンケートに回答するまで、これを繰り返します。Repeat until enough respondents have answered the survey.

アンケートの回答を表すクラスが必要なので、ここでそれを追加します。You'll need a class to represent a survey response, so add that now. null 許容のサポートを有効にします。Enable nullable support. 次のコードに示すように、Id プロパティとそれを初期化するコンストラクターを追加します。Add an Id property and a constructor that initializes it, as shown in the following code:

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

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

次に、static メソッドを追加し、ランダム ID を生成することで新しい参加者を作成します。Next, add a static method to create new participants by generating a random ID:

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

このクラスの主な役割は、アンケートの質問に対する参加者の回答を生成することです。The main responsibility of this class is to generate the responses for a participant to the questions in the survey. この役割には、いくつかの手順があります。This responsibility has a few steps:

  1. アンケートへの参加を依頼します。Ask for participation in the survey. 回答者が同意しない場合は、応答の欠落 (つまり null) が返されます。If the person doesn't consent, return a missing (or null) response.
  2. 各質問を表示し、回答を記録します。Ask each question and record the answer. 回答も欠落する (つまり null になる) 可能性があります。Each answer may also be missing (or null).

SurveyResponse クラスに次のコードを追加します。Add the following code to your SurveyResponse class:

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

アンケートの回答用のストレージは Dictionary<int, string>? であり、null が可能であることを示しています。The storage for the survey answers is a Dictionary<int, string>?, indicating that it may be null. 新しい言語機能を使用して、コンパイラーと後日コードを読む人の両方に対して、設計意図が宣言されています。You're using the new language feature to declare your design intent, both to the compiler and to anyone reading your code later. 先に null 値のチェックを行わずに surveyResponses を逆参照した場合は、コンパイラの警告が表示されます。If you ever dereference surveyResponses without checking for the null value first, you'll get a compiler warning. AnswerSurvey メソッドで警告が表示されないのは、上記で surveyResponses 変数が null 以外の値に設定されたことをコンパイラが判断できるためです。You don't get a warning in the AnswerSurvey method because the compiler can determine the surveyResponses variable was set to a non-null value above.

欠落している回答に対して null を使用すると、null 許容参照型を処理するための重要なポイントが強調表示されます。目標は、プログラムからすべての null 値を削除することではありません。Using null for missing answers highlights a key point for working with nullable reference types: your goal isn't to remove all null values from your program. 本当の目標は、記述しているコードで設計の意図が確実に表されるようにすることです。Rather, your goal is to ensure that the code you write expresses the intent of your design. 欠落値は、コードでの表現に必要な概念です。Missing values are a necessary concept to express in your code. null 値は、これらの欠落値を表現する明確な方法です。The null value is a clear way to express those missing values. すべての null を削除しようとしても、null を使わずにそれらの欠落値を表すための他の何らかの方法を定義することになるだけです。Trying to remove all null values only leads to defining some other way to express those missing values without null.

次に、SurveyRun クラス内に PerformSurvey メソッドを記述する必要があります。Next, you need to write the PerformSurvey method in the SurveyRun class. SurveyRun クラスに次のコードを追加します。Add the following code in the SurveyRun class:

private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
    int repondentsConsenting = 0;
    respondents = new List<SurveyResponse>();
    while (repondentsConsenting < numberOfRespondents)
    {
        var respondent = SurveyResponse.GetRandomId();
        if (respondent.AnswerSurvey(surveyQuestions))
            repondentsConsenting++;
        respondents.Add(respondent);
    }
}

ここでも、null を許容する List<SurveyResponse>? の選択によって、応答で null が可能であることが示されす。Here again, your choice of a nullable List<SurveyResponse>? indicates the response may be null. これは、アンケートがまだ回答者に表示されていないことを示します。That indicates the survey hasn't been given to any respondents yet. 十分な数の同意が得られるまで回答者が追加されることに注意してください。Notice that respondents are added until enough have consented.

アンケートを実行する最後の手順は、Main メソッドの最後にアンケートを実行する呼び出しを追加することです。The last step to run the survey is to add a call to perform the survey at the end of the Main method:

surveyRun.PerformSurvey(50);

アンケートの回答を調べるExamine survey responses

最後の手順は、アンケートの結果を表示することです。The last step is to display survey results. 記述したクラスの多くにコードを追加します。You'll add code to many of the classes you've written. このコードでは、null 許容参照型と null 非許容参照型を区別する値を示します。This code demonstrates the value of distinguishing nullable and non-nullable reference types. SurveyResponse クラスに次の 2 つの式形式メンバーを追加することから始めます。Start by adding the following two expression-bodied members to the SurveyResponse class:

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

surveyResponses は null 許容参照型であるため、それを逆参照する前にチェックが必要です。Because surveyResponses is a nullable reference type, null checks are necessary before de-referencing it. Answer メソッドからは null 非許容の文字列が返されます。そのため、null 合体演算子を使用して、不足している回答のケースをカバーする必要があります。The Answer method returns a non-nullable string, so we have to cover the case of a missing answer by using the null-coalescing operator.

次に、次の 3 つの式形式メンバーを SurveyRun クラスに追加します。Next, add these three expression-bodied members to the SurveyRun class:

public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];

AllParticipants メンバーでは、respondents 変数は null の場合があるが、戻り値を null にすることはできないことを考慮する必要があります。The AllParticipants member must take into account that the respondents variable might be null, but the return value can't be null. この式を ?? とその後ろの空のシーケンスを削除することで変更すると、コンパイラーによって、メソッドで null が返され、その戻り値のシグネチャで null 非許容型が返される可能性があることが警告されます。If you change that expression by removing the ?? and the empty sequence that follows, the compiler warns you the method might return null and its return signature returns a non-nullable type.

最後に、次のループを Main メソッドの末尾に追加します。Finally, add the following loop at the bottom of the Main method:

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

基になるインターフェースを非許容参照型を返すように設計しているため、このコードでは null のチェックは必要ありません。You don't need any null checks in this code because you've designed the underlying interfaces so that they all return non-nullable reference types.

コードを取得するGet the code

csharp/NullableIntroduction フォルダーの samples リポジトリから、完成したチュートリアルのコードを取得できます。You can get the code for the finished tutorial from our samples repository in the csharp/NullableIntroduction folder.

null 許容参照型と null 非許容参照型の間で型宣言を変更することで試してください。Experiment by changing the type declarations between nullable and non-nullable reference types. それによって生成される警告が変わることを確認して、null を間違って逆参照することがないようにしてください。See how that generates different warnings to ensure you don't accidentally dereference a null.

次の手順Next steps

既存のアプリケーションを null 許容参照型を使用するように移行することについてさらに詳しく学習します。Learn more by migrating an existing application to use nullable reference types: