null 許容参照型では、null 許容値型で値型を補完するのと同じように、参照型を補完します。 型に を追加することで、変数が ?であることを宣言します。 たとえば、string? は、null が許容される string を表します。 これらの新しい型を使用すると、設計の意図をより明確に表現できます。変数によっては 常に値が必要な 変数もあれば、 値が欠落している変数もあります。
このチュートリアルでは、以下の内容を学習します。
- null 許容参照型と null 非許容参照型を設計に組み込む。
- コード全体でヌラブル参照型チェックを有効にします。
- コンパイラがこれらの設計上の決定を強制するコードを記述します。
- 自分の設計の中で null 許容参照機能を使用する。
前提 条件
- 最新の .NET SDK
- Visual Studio Code エディター
- C# DevKit
このチュートリアルでは、Visual Studio や .NET CLI など、C# と .NET について理解していることを前提としています。
null 許容参照型と null 非許容参照型を設計に組み込む
このチュートリアルでは、アンケートを実行するモデルを作成するライブラリを作成します。 このコードでは、null 許容参照型と非許容参照型の両方を使って、現実世界の概念を表現しています。 アンケートの質問を null にすることはできません。 回答者は、質問に答えたくない場合があります。 この場合、応答は null 可能性があります。
このサンプル用に記述するコードはその意図を表し、コンパイラはその意図を強制します。
アプリケーションを作成し、null 許容参照型を有効にする
Visual Studio で、または dotnet new consoleを使用してコマンド ラインから新しいコンソール アプリケーションを作成します。 アプリケーションに NullableIntroductionという名前を付けます。 アプリケーションを作成したら、 有効な null 許容注釈コンテキストでプロジェクト全体がコンパイルされることを指定する必要があります。
.csproj ファイルを開き、Nullable 要素に PropertyGroup 要素を追加します。 その値を enableに設定します。 C# 11 / .NET 7 より前に作成されたプロジェクトで null 許容参照型 機能をオプトインする必要があります。 この機能を有効にすると、既存の参照変数宣言は null 非許容参照型になります。 この決定は、既存のコードに適切な null チェックがない可能性がある問題を見つけるのに役立ちますが、元の設計意図を正確に反映していない可能性があります。
<Nullable>enable</Nullable>
アプリケーションの型を設計する
このアンケート アプリケーションでは、次のクラスを作成する必要があります。
- 質問のリストをモデル化するクラス。
- アンケートに連絡したユーザーの一覧をモデル化するクラス。
- 調査を受けた人からの回答をモデル化するクラス。
これらの型では、null 許容参照型と null 非許容参照型の両方を使用して、必要なメンバーと省略可能なメンバーを表します。 null 許容参照型により、次の設計意図が明確に伝わります。
- アンケートの一部である質問を null にすることはできません。空の質問をしても意味がありません。
- 回答者が null になることはありません。 連絡を取ったユーザー (参加を拒否した回答者も含む) を追跡する必要があります。
- 質問に対する応答はすべて null である可能性があります。 回答者は、一部またはすべての質問への回答を拒否できます。
null値を許可する参照型に慣れている可能性があるため、null 非許容インスタンスを宣言する他の機会を逃す可能性があります。
- 質問のコレクションは null を許容しない必要があります。
- 回答者のコレクションは null を許容しない必要があります。
コードを記述すると、参照のデフォルトで非 null 参照型を使用することにより、NullReferenceExceptionが発生する可能性のある一般的なミスを回避することができると理解できます。 このチュートリアルの 1 つのレッスンは、どの変数を 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 非許容参照型であるため、コンパイラは警告を発行しません。
次に、publicという名前の SurveyRun クラスを作成します。 このクラスには、次のコードに示すように、アンケートに質問を追加する 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 以外の値に初期化するか、コンパイラが警告を発行する必要があります。 コンパイラが null 非許容コントラクトを強制するのに役立つので、 AddQuestion の 2 番目のオーバーロードに null チェックはありません。この変数を null 非許容として宣言しました。 コンパイラは潜在的な null 割り当てについて警告しますが、ランタイムの null 値は引き続き可能です。 パブリック API の場合は、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>?です。 新しい言語機能を使用して、コンパイラと後でコードを読むすべてのユーザーにデザインの意図を宣言します。 最初にsurveyResponses値を確認せずにnullを逆参照すると、コンパイラ警告が表示されます。 コンパイラは、AnswerSurvey変数が前のコードで null 以外の値に設定されたことを判断できるため、surveyResponses メソッドでは警告を受け取りません。
不足している回答に null を使用すると、null 許容参照型を操作するための重要なポイントが強調表示されます。目標は、プログラムからすべての null 値を削除することではありません。 むしろ、作成するコードが設計の意図を表していることを確認することが目標です。 欠損値は、コードで表現するために必要な概念です。
null 値は、これらの欠損値を明確に表す方法です。 すべての null 値を削除しようとすると、 nullせずにそれらの欠損値を表す他の方法が定義されます。
次に、PerformSurvey クラスに SurveyRun メソッドを記述する必要があります。
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