チュートリアル: null 許容参照型と null 非許容参照型を使用して設計意図をもっと明確に示す
null 許容参照型では、null 許容値型で値型を補完するのと同じように、参照型を補完します。 型に ?
を追加することで、変数が null 許容参照型であることを宣言します。 たとえば、string?
は、null が許容される string
を表します。 これらの新しい型を使用すると、設計の意図をより明確に表すことができます。一部の変数 常に値必要があり、他の変数 値不足している可能性があります。
このチュートリアルでは、次の方法について説明します。
- null 許容参照型と null 非許容参照型を設計に組み込む。
- コード全体でヌラブル参照型チェックを有効にします。
- コンパイラがこれらの設計上の決定を強制するコードを記述します。
- 自分の設計の中で null 許容参照機能を使用する。
前提 条件
C# コンパイラを含め、.NET を実行するようにマシンを設定する必要があります。 C# コンパイラは、Visual Studio 2022または .NET SDKで使用できます。
このチュートリアルでは、Visual Studio や .NET CLI など、C# と .NET について理解していることを前提としています。
null 許容参照型と null 非許容参照型を設計に組み込む
このチュートリアルでは、アンケートを実行するモデルを作成するライブラリを作成します。 このコードでは、null 許容参照型と非許容参照型の両方を使って、現実世界の概念を表現しています。 アンケートの質問を null にすることはできません。 回答者は、質問に答えたくない場合があります。 この場合、応答は null
可能性があります。
このサンプル用に記述するコードはその意図を表し、コンパイラはその意図を強制します。
アプリケーションを作成し、null 許容参照型を有効にする
Visual Studio で、または dotnet new console
を使用してコマンド ラインから新しいコンソール アプリケーションを作成します。 アプリケーションに NullableIntroduction
という名前を付けます。 アプリケーションを作成したら、プロジェクト全体が、有効な null 許容注釈コンテキストでコンパイルされるように指定する必要があります。 .csproj ファイルを開き、PropertyGroup
要素に Nullable
要素を追加します。 その値を enable
に設定します。 C# 11 より前のプロジェクトでは、null 許容参照型機能にオプトインする必要があります。 これは、機能をオンにすると、既存の参照変数宣言が null 非許容参照型になるためです。 この決定は、既存のコードに適切な null チェックがない問題を見つけるのに役立ちますが、元の設計意図を正確に反映していない可能性があります。
<Nullable>enable</Nullable>
.NET 6 より前の新しいプロジェクトには、Nullable
要素は含まれていません。 .NET 6 以降では、新しいプロジェクトにはプロジェクト ファイルに <Nullable>enable</Nullable>
要素が含まれています。
アプリケーションの型を設計する
このアンケート アプリケーションでは、いくつかのクラスを作成する必要があります。
- 質問のリストをモデル化するクラス。
- アンケートに連絡したユーザーの一覧をモデル化するクラス。
- 調査を受けた人からの回答をモデル化するクラス。
これらの型では、null 許容参照型と null 非許容参照型の両方を使用して、必要なメンバーと省略可能なメンバーを表します。 null 許容参照型により、次の設計意図が明確に伝わります。
- アンケートの一部である質問を null にすることはできません。空の質問をしても意味がありません。
- 回答者が null になることはありません。 参加を拒否した回答者であっても、連絡したユーザーを追跡する必要があります。
- 質問に対する応答はすべて null である可能性があります。 回答者は、一部またはすべての質問への回答を拒否できます。
C# でプログラミングした場合は、null
値を許可する参照型に慣れている可能性があるため、null 非許容インスタンスを宣言する他の機会を逃した可能性があります。
- 質問のコレクションは null を許容しない必要があります。
- 回答者のコレクションは null を許容しない必要があります。
コードを記述すると、参照の既定値として非null参照型を使用することで、NullReferenceExceptionにつながる可能性のある一般的なミスを避けることができます。 このチュートリアルの教訓の一つは、どの変数を null
するかしないかを決めたことです。 言語では、これらの決定を表す構文は提供されませんでした。 今はそうです。
ビルドするアプリは、次の手順を実行します。
- アンケートを作成し、質問を追加します。
- アンケートの回答者の擬似ランダム セットを作成します。
- 完了したアンケートのサイズが目標値に達するまで、回答者に連絡します。
- アンケートの回答に関する重要な統計を書き出します。
null 許容参照型と null 非許容参照型を含むアンケートを作成する
最初に作成するコードによってアンケートが作成されます。 アンケートの質問とアンケートの実行をモデル化するクラスを作成します。 アンケートには、回答の形式によって区別される 3 種類の質問があります。はい/いいえの回答、回答数、テキスト回答です。 public SurveyQuestion
クラスを作成します。
namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}
コンパイラでは、有効な null 許容注釈コンテキスト内のコードについては、すべての参照型変数の宣言が null 非許容参照型として解釈されます。 次のコードに示すように、質問テキストのプロパティと質問の種類を追加することで、最初の警告を確認できます。
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion
{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
}
}
QuestionText
をあなたが初期化していないため、コンパイラは非 null プロパティが初期化されていないことを示す警告を発行します。 デザインでは、質問テキストを 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
の 2 番目のオーバーロードには null チェックは必要ないため、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);
回答者を作成し、アンケートに対する回答を取得する
次に、アンケートに対する回答を生成するコードを記述します。 このプロセスには、いくつかの小さなタスクが含まれます。
- 回答者オブジェクトを生成するメソッドを作成します。 これらは、アンケートに記入するように求められたユーザーを表します。
- 回答者に質問をしたり、回答を収集したり、回答者が回答しなかったことを示したりするロジックを構築します。
- 十分な回答者がアンケートに回答するまで繰り返します。
アンケートの回答を表すクラスが必要になるので、ここで追加します。 null 許容のサポートを有効にします。 次のコードに示すように、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());
このクラスの主な役割は、アンケートの質問に対する参加者の回答を生成することです。 この責任には、いくつかの手順があります。
- アンケートへの参加を依頼します。 回答者が同意しない場合は、応答の欠落 (つまり null) が返されます。
- 各質問をし、回答を記録します。 各回答が見つからない (または 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
を逆参照すると、コンパイラ警告が表示されます。 コンパイラは、surveyResponses
変数が上記の null 以外の値に設定されたことを判断できるため、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);
}
}
ここでも、null を許容する List<SurveyResponse>?
の選択によって、応答で null が可能であることが示されす。 これは、アンケートがまだ回答者に与えられていないことを示しています。 十分に同意するまで回答者が追加されていることに注意してください。
アンケートを実行する最後の手順は、Main
メソッドの最後にアンケートを実行する呼び出しを追加することです。
surveyRun.PerformSurvey(50);
アンケートの回答を調べる
最後の手順では、アンケート結果を表示します。 記述した多くのクラスにコードを追加します。 このコードは、null 許容参照型と null 非許容参照型を区別する値を示しています。 まず、次の 2 つの式形式のメンバーを SurveyResponse
クラスに追加します。
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
surveyResponses
は null 許容参照型であるため、参照を解除する前に null チェックが必要です。 Answer
メソッドは null 非許容文字列を返すので、null 結合演算子を使用して、不足している回答のケースをカバーする必要があります。
次に、次の 3 つの式形式のメンバーを 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
を返す可能性があることを警告し、その戻りシグネチャは 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");
}
}
基になるインターフェイスがすべて null 非許容参照型を返すように設計されているため、このコードで null
チェックを行う必要はありません。
コードを取得する
完成したチュートリアルのコードは、csharp/NullableIntroduction フォルダーにある サンプル リポジトリから入手できます。
null 許容参照型と null 非許容参照型の間で型宣言を変更することで試してください。 null
を誤って逆参照しないように、さまざまな警告がどのように生成されるかを確認します。
次の手順
Entity Framework を使用するときに null 許容参照型を使用する方法について説明します。
.NET