C# Tips | コレクション・LINQ:null安全List

C# C#
スポンサーリンク

はじめに:「null安全List」は“落とし穴を先に埋めておく道具”

業務コードでありがちな例として、

List<string> names = null;

// ここで落ちる
foreach (var n in names)
{
    Console.WriteLine(n);
}
C#

「null の可能性がある List に対して、うっかりそのまま LINQ や foreach を回してしまう」
これが、実務でかなり頻出するバグのパターンです。

ここで目指したいのは、

「List が null でも“空のコレクション”として扱えるようにしておく」

という発想です。
“null かもしれない List” を、先に「null 安全な形」に変換してから使うことで、
以降のコードをシンプルに、そして安全にできます。


基本アイデア:null を「空のコレクション」に正規化する

拡張メソッド ToSafeList / OrEmpty を用意する

まずは、「null かもしれない IEnumerable<T> を、絶対に null にならない列に変換する」
という小さなユーティリティを作ります。

using System;
using System.Collections.Generic;
using System.Linq;

public static class EnumerableExtensions
{
    public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T>? source)
    {
        return source ?? Enumerable.Empty<T>();
    }

    public static List<T> ToSafeList<T>(this IEnumerable<T>? source)
    {
        return source?.ToList() ?? new List<T>();
    }
}
C#

ここでの重要ポイントは、
「null だったら Enumerable.Empty<T>()new List<T>() に置き換える」
という一点だけです。

これで、「null かもしれない」を「空かもしれない」に変換できます。
“空”なら、LINQ も foreach も安全に動きます。


例1:foreach で回すときに null を気にしなくてよくする

Before:毎回 null チェックを書くコード

List<string>? names = GetNamesOrNull();

if (names != null)
{
    foreach (var n in names)
    {
        Console.WriteLine(n);
    }
}
C#

毎回 if (names != null) を書くのはダルいし、
書き忘れたところで NullReferenceException が飛びます。

After:OrEmpty を通してから回す

List<string>? names = GetNamesOrNull();

foreach (var n in names.OrEmpty())
{
    Console.WriteLine(n);
}
C#

names が null の場合、OrEmpty() が「空の列」に変換してくれるので、
foreach は単に「何も回らない」だけで終わります。

ここでの重要ポイントは、

「null のときに“例外”ではなく“空”として扱う、というポリシーを決める」

ことです。
このポリシーをユーティリティに閉じ込めておくと、呼び出し側のコードが一気にスッキリします。


例2:LINQ で集計するときに null を気にしない

Before:LINQ の前に null チェックが必要

List<int>? scores = GetScoresOrNull();

int total = 0;

if (scores != null)
{
    total = scores.Sum();
}
C#

After:OrEmpty を挟んでから LINQ を使う

List<int>? scores = GetScoresOrNull();

int total = scores.OrEmpty().Sum();
C#

scores が null なら、OrEmpty() が空の列に変換し、
Sum() の結果は 0 になります(LINQ の仕様)。

ここでの重要ポイントは、

「null のときの“意味あるデフォルト”を決めておく」

ことです。
合計なら 0、件数なら 0、最大値・最小値なら別途扱いを決める、など。
少なくとも「NullReferenceException で落ちる」よりは、
“空として扱う”ほうが実務では扱いやすい場面が多いです。


例3:ToSafeList で「絶対 null にならない List」を受け取る

ToSafeList の使いどころ

OrEmpty()IEnumerable<T> を返しますが、
「この先は List として扱いたい」という場面も多いです。

List<string>? names = GetNamesOrNull();

List<string> safeList = names.ToSafeList();

// ここから先は safeList が null でないことが保証される
Console.WriteLine($"件数: {safeList.Count}");
C#

names が null の場合でも、ToSafeList() は空の List<string> を返します。
これで、「この変数は絶対に null ではない」と言い切れるようになります。

ここでの重要ポイントは、

「境界で“null かもしれない”を“null ではない”に変換しておく」

という設計です。
メソッドの入り口や、外部からの入力を受け取った直後に ToSafeList を通しておけば、
内部のロジックでは null を気にせずに List を扱えます。


もう一歩:null 許容参照型(nullable reference types)との組み合わせ

List<T>? と List<T> を意識的に使い分ける

C# 8 以降の「null 許容参照型」を使っている場合、
型システム的にも「null かもしれない List」と「null ではない List」を区別できます。

List<string>? maybeNames = GetNamesOrNull();
List<string> names = maybeNames.ToSafeList(); // ここで“非 null”に正規化
C#

このように、

外部から来るもの(DB、API、UI)は List<T>?
内部で使うものは List<T>(ToSafeList 済み)

というルールにしておくと、
コンパイラの警告も味方にしながら、null 安全なコードを書けるようになります。

ここでの重要ポイントは、

「null を“どこまで許すか”を設計として決める」

ことです。
ユーティリティは、その設計を支えるための“変換ポイント”として使います。


実務で意識してほしいこと

「null を返さない」設計に寄せていく

本音を言うと、
「List を返すメソッドは、そもそも null を返さず、空の List を返す」
という設計にできるのが理想です。

// 悪い例(null の可能性がある)
List<User>? GetUsers();

// 良い例(null は返さない)
List<User> GetUsers()
{
    return _repository.LoadUsers() ?? new List<User>();
}
C#

ただ、既存コードや外部ライブラリでは、
どうしても List<T>? が返ってくることがあります。
そういうときにこそ、OrEmptyToSafeList のようなユーティリティが効いてきます。

「null チェックだらけのコード」から卒業する

if (list != null) があちこちに散らばっているコードは、
読みづらいし、どこかでチェック漏れが起きます。

「null かもしれないものは、最初に“空”に正規化してしまう」
というスタイルに切り替えると、
LINQ も foreach も、ずっと気楽に書けるようになります。


まとめ:「null安全Listユーティリティ」は“例外ではなく空として扱うためのクッション”

ポイントを整理すると、こうなります。

OrEmpty() で「null かもしれない列」を「空かもしれない列」に変換する。
ToSafeList() で「null かもしれない列」から「絶対 null ではない List」を作る。
foreach や LINQ の前に一度だけこれらを通しておけば、以降のコードで null を気にしなくてよくなる。
外部との境界で null を受け取り、内部では null を排除する、という設計を意識する。

こういう小さなユーティリティを一つずつ持っておくと、
「NullReferenceException に怯えながら書くコード」から、
「安心して LINQ とコレクションを使い倒せるコード」に、じわじわ変わっていきます。

タイトルとURLをコピーしました