【C#】Nullの悲劇 - その例外、回避できたはずだ

## はじめに

C#開発者なら誰もが一度は経験したことがあるでしょう。あの恐怖の瞬間を。

```
System.NullReferenceException: Object reference not set to an instance of an object.
```

コンソールアプリなら即座にクラッシュ、Webアプリならエラーページ、デスクトップアプリなら予期しない終了。

そう、**NullReferenceException**こそが、多くのC#開発者にとって「初めての恐怖体験」なのです。

最近のC#コードを読んでいると、こんな記号を見かけることはありませんか？

```csharp:C#
string? name = user?.Name;
if (name is not null)
{
    Console.WriteLine($"Hello, {name}!");
}
```

「`?`って何？なんで`string`じゃなくて`string?`なの？」

この疑問こそが、今日のお話の出発点です。

:::note info
今回は、意外と知られていない **Nullable Reference Types** の使い方と効果を、実例とともに紹介します。
:::


## ある日突然の例外（典型例の紹介）

金曜日の夕方、あなたは自信満々でコードをデプロイしました。テストも通っているし、ローカルでも完璧に動いている。しかし、月曜日の朝一番で緊急のバグ報告が...

```csharp:C#
public class UserService
{
    public string GetUserDisplayName(int userId)
    {
        var user = GetUser(userId);
        var displayName = (user?.FirstName + " " + user?.LastName)?.Trim();
        return displayName ?? "Unknown"; // ✅ null対策されたバージョン
    }

    private User? GetUser(int userId)
    {
        return _context.Users.FirstOrDefault(u => u.Id == userId);
    }
}
```

一見問題なさそうに見えますが、より明確に「NullReferenceException」が起こる例を見てみましょう。

```csharp:C#
public string GetUserEmail(int userId)
{
    var user = GetUser(userId);
    return user.Email.ToUpper(); // 💥 userがnullの場合にNullReferenceException!!
}
```

「でも、テストでは動いていたのに...」

そうです。テストデータには常にユーザーが存在していたからです。本番環境で削除されたユーザーIDが渡された瞬間、**悲劇**が起こりました。

## 悲劇の原因分析

### 1. 型システムが「nullかもしれない」を表現できない

C# 7以前の参照型は、すべて暗黙的にnull許容です。つまり、戻り値が `string` となっていても、実際には `null` が返る可能性がありました。

```csharp:C#
public string GetUserName(int userId)
{
    var user = FindUser(userId);
    return user?.Name; // nullかもしれないのに、戻り値はstring
}
```

### 2. 静的チェックが効かない

コンパイラはnullの可能性を知らないので、危険なコードでも平然と通ってしまいます。

```csharp:C#
string name = GetUserName(123);
Console.WriteLine(name.Length); // 💥 コンパイルエラーにはならない
```

### 3. ドキュメントと実装の乖離

「nullを返す可能性がある」とコメントに書いても、それは単なる“お願い”でしかありません。型システムで保証されない限り、見落としや誤解は避けられません。

## C# 8.0以降の光：Nullable Reference Types の登場

2019年、ついに C# に救世主が現れました。それが **Nullable Reference Types** です。

### `#nullable enable` の魔力

```csharp:C#
#nullable enable

public string? GetUserName(int userId)
{
    var user = FindUser(userId);
    return user?.Name;
}
```

* `string?`：nullを返す可能性があることを明示
* `string`：nullを返さないという契約

### 警告で守ってくれる世界

```csharp:C#
string? name = GetUserName(123);
Console.WriteLine(name.Length); 
// ⚠️ Warning CS8602: Dereference of a possibly null reference.
```

これにより、**実行前にエラーの芽を摘む**ことができます。


## 実践パターン集

### 1. 明示的な null チェック

```csharp:C#
if (name != null)
{
    Console.WriteLine(name.Length);
}
```

### 2. パターンマッチング

```csharp
if (name is not null)
{
    Console.WriteLine($"Hello, {name}!");
}
```

### 3. デフォルト値を返す

```csharp:C#
return name ?? "Unknown User";
```

### 4. 条件演算子でスマートに

```csharp:C#
Console.WriteLine(name?.Length > 0 ? $"Name: {name}" : "No name available");
```

### 5. 早期リターンで安全に

```csharp:C#
if (name is null)
{
    Console.WriteLine("User not found");
    return;
}

ProcessValidUser(name); // この時点でnameはnon-null
```

## よくある落とし穴と回避テク

### ❌ Null抑制演算子（!）の乱用

```csharp:C#
string name = GetUserName(userId)!; // nullだったら即死
```

→ 使ってもいいが、「ここは絶対にnullじゃない」と言える場合だけ！


## Nullable導入時の注意点

### 外部ライブラリとの連携

```csharp:C#
string? externalData = ExternalLib.GetData() as string;
```

### Entity Framework

```csharp:C#
public string Name { get; set; } = string.Empty;
public string? Email { get; set; }
```

### JSONデシリアライズ

```csharp:C#
if (response?.Data is not null)
{
    ProcessUserData(response.Data);
}
```


## プロジェクトへの導入方法

### `.editorconfig`

```ini
[*.cs]
dotnet_style_nullable_annotations = true:suggestion
dotnet_style_nullable_context = true:suggestion
```

### `.csproj`

```xml
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
```

## 事故歴カタログ（実話風エピソード）

### 1. 逆さま早期リターン事件

```csharp:C#
if (user != null) return; // ❌ 本当は user == null の時にリターンしたかった
Console.WriteLine(user.Name); // 💥 NullReferenceException
```

### 2. テストの罠

```csharp:C#
Assert.AreEqual("Test User", GetUserName(testUser.Id));
// 本番では削除済みIDが渡されてnull、そして Lengthで爆発
```

### 3. null安全のつもりが...

```csharp:C#
var name = user?.Name;
Console.WriteLine(name.ToString()); // 💥 nameがnullなら例外
```

## おわりに

Nullable Reference Types は、**“Null”という不確実性に型レベルで立ち向かう武器**です。

たった1行の `#nullable enable` が、あなたのバグを減らし、安心をもたらします。

次に例外が起きた時、思い出してください。

> 「その例外、回避できたはずだ」

## 🔗 参考リンク（日本語）

* [Nullable 参照型 - C# リファレンス | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/csharp/nullable-references)  
  └ C# での null 許容参照型（Nullable Reference Types）の概要と構文解説（日本語）

## 🔗 参考リンク（英語）

* [Nullable reference types - C# reference | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references)
* [Nullable reference types tutorial - C# | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/nullable-reference-types)s)  
  └ 実践的な導入方法をステップバイステップで学べるチュートリアル

---