第15章:nullabilityは契約☂️(Null許可の怖さ)
15.1 この章でできるようになること🎯✨
- 「null を許す/許さない」が、利用者にとっての契約だと説明できるようになる😊☂️
- C# の **nullable reference types(NRT)**で、契約をコードに埋め込む書き方がわかる🧩
- 「あとから厳しくする(
string?→string)」が なぜ地味に危険かを体感できる😇⚡ - public API の null 受け取りを、実行時にもちゃんと守る方法がわかる🛡️✨
15.2 まず結論:null は「仕様」だから、軽く扱うと事故る😵☂️

参照型は本質的に null を持てるけど、NRT を使うと「ここは null にならない約束だよ」を コンパイラに伝えられるようになります😊 この「約束」が、まさに 契約(Contract) そのものです☂️✨ (Microsoft Learn)
そして超大事ポイント👇
- NRT は コンパイル時の仕組み(=警告で教えてくれる)
- でも実行時には、呼び出し側が気合で null を渡すこともできる😇
- だから 公開API(public)では実行時チェックも必要 になることが多い🛡️(例:
ArgumentNullException.ThrowIfNull) (Microsoft Learn)
15.3 用語をゆるっと整理🧁📘
non-nullable と nullable🍀🌧️
string:non-nullable(null は来ない想定🍀)string?:nullable(null が来る想定🌧️)
この “?” は「型の趣味」じゃなくて、利用者に対する説明書です📘✨
15.4 「契約」として影響がデカい場所トップ5⚡🧨
- 引数(parameter):null を渡していい?ダメ?
- 戻り値(return):null が返る可能性ある?
- プロパティ(property):読み取り時に null あり?
- DTO/JSON:欠損(未指定)と null を区別する?
- イベント/コールバック:渡される値が null あり?
ここがブレると、利用者のコードに「?. と ?? と null チェックの森」が生えます🌳🌳🌳😵
15.5 “後から厳しくする” が危険な理由💣(string? → string)
何が起きるの?😇
たとえば v1 でこうだった👇
public string? FindNickname(int userId);
利用者は「null が返る前提」で書くよね👇
var nick = api.FindNickname(id);
Console.WriteLine(nick ?? "(no nickname)");
ここで v2 で「やっぱ null 返さないわ!」とこう変える👇
public string FindNickname(int userId);
一見「安全になった」っぽいけど、利用者側では👇
- 以前は
string?の前提で書いていたコードが、 警告の変化・解析結果の変化・ビルド設定次第でビルド落ちにつながることがある⚡ - 実際に .NET ライブラリ側でも、nullability 注釈の変更で 新しい警告が出ることが「破壊的に感じられる」ケースがある(=互換性に影響する)(Microsoft Learn)
つまり、nullability は 公開APIの一部(契約の一部) なんだよね☂️📘
15.6 “契約としての null” を決める3パターン🍡✨

パターンA:null を「許さない」🍀(基本これ推し)
- 引数で null を受けない
- 戻り値で null を返さない
- 「無い」状態は別の表現にする(
Try~/Result/Option的に扱う)🎁
パターンB:null を「意味のある値」として使う🌧️(慎重に)
- 例:
nickname == nullは「未設定」を意味する - その代わり、ドキュメントとテストで固定する🧪📝
パターンC:外部入力だけは null が来る前提で「受ける」🚪
- JSON や外部API、DB などは “汚れてる” 前提😇
- 入口で正規化して、内部では non-nullable を保つ✨
15.7 プロジェクト設定:nullable を ON にする💡☂️
nullable はプロジェクト全体で有効化できます👇(csproj)
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
これで、参照型が基本 non-nullable として扱われ、必要なところだけ ? を付ける運用がしやすくなります😊 (Microsoft Learn)
ファイル単位で切り替えるなら #nullable も使えます👇
(移行中や自動生成コードまわりで便利!)
#nullable enable
// ここから null 解析が有効
#nullable restore
#nullable enable/disable/warnings/annotations のバリエーションもあるよ☂️ (Microsoft Learn)
15.8 公開APIは「警告」だけに頼らず、実行時でも守る🛡️✨
NRT は “コンパイラが親切に教えてくれる” 仕組みだけど、呼び出し側が警告を無視することもあります😇 なので public API では、入口でガードを置くのが安全です👇
public void SetName(string name)
{
ArgumentNullException.ThrowIfNull(name);
// name はここから non-null として扱える
}
この ThrowIfNull は、公開APIの契約を実行時にも守る定番です🛡️ (Microsoft Learn)
15.9 ちょい上級:属性で「null の条件」を説明する🧠✨
「このメソッドが true を返すとき、out は null じゃないよ」みたいな条件付きの契約、ありますよね😊
そんなときに使えるのが、コンパイラが理解する属性たちです👇
NotNullWhen(true)MaybeNull/NotNullAllowNull/DisallowNullMemberNotNullWhenなど
これらは nullable 静的解析を強化するための属性として整理されています📘 (Microsoft Learn)
例:Try パターンの契約(成功時だけ out が non-null)👇
using System.Diagnostics.CodeAnalysis;
public static bool TryGetEmail(int userId, [NotNullWhen(true)] out string? email)
{
if (userId <= 0)
{
email = null;
return false;
}
email = "a@example.com";
return true;
}
利用者はこう書けて、警告が減ります😊✨
if (TryGetEmail(id, out var email))
{
Console.WriteLine(email.Length); // ここで email は non-null 扱い
}
15.10 実習①:null 契約を “固定” して、壊れ方を体験する🧪⚡
ここは「提供側(ライブラリ)」と「利用側(コンソール)」の2プロジェクト構成を想定します🛠️✨
Step 1:v1 の API を作る🌱
public static class UserNames
{
// v1: ニックネームは無いことがある(null)
public static string? GetNickname(int userId)
=> userId % 2 == 0 ? "Mia" : null;
}
利用側:
var nick = UserNames.GetNickname(1);
Console.WriteLine(nick.ToUpper()); // ← 警告が出るはず⚠️
✅ ここで利用者は「null あり前提」の防御を書く必要が出る☂️
Step 2:v1.1 で “契約を決め直す” 🎯(null を返さない方針)
「null を返さない」のが契約なら、返せる値を必ず返すようにする👇
public static class UserNames
{
// v1.1: null を返さない契約に変更(代わりに空文字を返す例)
public static string GetNicknameOrEmpty(int userId)
=> userId % 2 == 0 ? "Mia" : "";
}
利用側:
var nick = UserNames.GetNicknameOrEmpty(1);
Console.WriteLine(nick.ToUpper()); // 警告なし
✅ “null が無い世界” になると、利用者コードがスッキリします😊✨ ⚠️ ただし「空文字の意味」を仕様として固定しないと別の事故が起きるので注意🧨
15.11 実習②:DTO/JSON の null を契約として扱う🍡📦
JSON は「フィールド未指定」と「null」が混ざりがちで、ここが契約の地雷原です😇💣
System.Text.Json は “nullability を尊重する” モードがある☂️
RespectNullableAnnotations を有効にすると、non-nullable なメンバーに null が入るのを検出しやすくなります(ただし適用範囲や制限あり)📘 (Microsoft Learn)
例:オプションで有効化する
using System.Text.Json;
var options = new JsonSerializerOptions
{
RespectNullableAnnotations = true
};
例:プロジェクト全体で既定値として有効化する(機能スイッチ)
csproj にこれを入れる方法もあります👇 (Microsoft Learn)
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.RespectNullableAnnotationsDefault"
Value="true" />
</ItemGroup>
DTO 側の設計のコツ🍀
- 「必須」は non-nullable(
string) - 「任意」は nullable(
string?) - “未指定” と “null” を区別したいなら、設計で表現する(別フィールド、別DTO、Result化など)🧩
15.12 AI 活用:null 契約のレビューを爆速にする🤖💨
使いどころ①:公開APIの「null ルール」を洗い出す🔍
- 「public なメソッドとプロパティを列挙して、
?の有無を要約して」 - 「
string?を使ってる理由(意味)を1行コメント案で添えて」
使いどころ②:Try~/Result の形に整える🧰
- 「この
null戻り値 API を、Try パターンに変換して」 - 「成功/失敗の契約を
NotNullWhenで表現して」
※AI の提案は “下書き” として使って、最終判断は人間がやるのが安全だよ😊🧠
15.13 章末チェックリスト✅☂️
- public API で
string?を使うとき、「null の意味」を説明できる📝 - 「null を返さない」方針なら、戻り値や DTO から null を排除できている🍀
- 外部入力(JSON等)は入口で正規化し、内部は non-nullable を維持している🚪✨
- public API の入口で
ThrowIfNullなど実行時ガードも入れている🛡️ (Microsoft Learn) - 条件付き契約(Try系)に属性を使える🧠 (Microsoft Learn)
15.14 ミニ理解テスト🎓🌸
stringとstring?の違いは「実行時の型の違い」?それとも「契約」?☂️- public API で NRT を有効にしても、なぜ実行時 null チェックが必要になりうる?😇
TryXxx(out T? value)が成功時に value 非nullだと伝えたい。どの属性が使える?🧠