5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

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

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

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

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

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

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

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

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

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

ある日突然の例外(典型例の紹介)

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

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」が起こる例を見てみましょう。

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 が返る可能性がありました。

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

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

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

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

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

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

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

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

#nullable enable の魔力

C#
#nullable enable

public string? GetUserName(int userId)
{
    var user = FindUser(userId);
    return user?.Name;
}
  • string?:nullを返す可能性があることを明示
  • string:nullを返さないという契約

警告で守ってくれる世界

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

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

実践パターン集

1. 明示的な null チェック

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

2. パターンマッチング

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

3. デフォルト値を返す

C#
return name ?? "Unknown User";

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

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

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

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

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

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

❌ Null抑制演算子(!)の乱用

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

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

Nullable導入時の注意点

外部ライブラリとの連携

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

Entity Framework

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

JSONデシリアライズ

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

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

.editorconfig

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

.csproj

<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

事故歴カタログ(実話風エピソード)

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

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

2. テストの罠

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

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

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

おわりに

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

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

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

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

🔗 参考リンク(日本語)

🔗 参考リンク(英語)


5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?