C# Tips | コレクション・LINQ:安全取得

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

はじめに:「安全取得」は“落ちないコード”を書くための必須スキル

業務システムで一番イヤなのは、「たまたまデータがなかっただけで落ちる」パターンです。
IndexOutOfRangeExceptionInvalidOperationExceptionNullReferenceException…どれも「ちゃんと安全取得していれば防げた」ものばかりです。

ここでいう「安全取得」は、

  • コレクションから「なかったらどうするか」を決めて値を取る
  • Dictionary から「なかったらどうするか」を決めて値を取る
  • LINQ で「0件のときに例外にしない」取り方をする

といった、「例外を出さずに、意図した形で値を扱うテクニック」のことです。


インデックスアクセスの安全取得

配列・List の「添字アクセス」は危険な刃

まず、典型的な危険パターンから。

var list = new List<string> { "A", "B", "C" };

var x = list[5]; // IndexOutOfRangeException
C#

業務では、「たまたま件数が少なかった」「フィルタした結果が短くなった」だけで、こういう例外が起きます。

安全に書くなら、「範囲内かどうか」を先に確認します。

int index = 5;

string? value = null;

if (0 <= index && index < list.Count)
{
    value = list[index];
}
C#

ここでの重要ポイントは、「“存在しないインデックス”を普通に使うと落ちる」という当たり前を、コードの中でちゃんと表現することです。


LINQ の安全取得:FirstOrDefault / SingleOrDefault / ElementAtOrDefault

First と FirstOrDefault の違い

LINQ で一番よく使うのが FirstFirstOrDefault です。

using System.Linq;

var numbers = new List<int> { 1, 2, 3 };

// 1件以上ある前提なら First
int first = numbers.First(); // 1
C#

でも、空のときは例外になります。

var empty = new List<int>();

int x = empty.First(); // InvalidOperationException
C#

「0件かもしれない」なら、FirstOrDefault を使います。

int firstOrDefault = empty.FirstOrDefault(); // 0(int の既定値)
C#

参照型なら null が返ります。

var names = new List<string>();

string? name = names.FirstOrDefault(); // null
C#

ここでの重要ポイントは、「First は“必ずある前提”、FirstOrDefault は“ないかもしれない前提”」という使い分けです。
業務的に「必ず1件以上あるべき」なら First で例外にしてもよく、「0件も普通にあり得る」なら FirstOrDefault を選びます。

条件付き FirstOrDefault

条件付きでも同じです。

var employees = new List<Employee>
{
    new Employee { No = 1, Name = "Sato" },
    new Employee { No = 2, Name = "Suzuki" },
};

var emp = employees.FirstOrDefault(e => e.No == 99); // 見つからない → null
C#

「見つからないこともある」前提なら、null を許容する設計にしておきます。

if (emp is null)
{
    // 見つからなかったときの処理
}
C#

Single / SingleOrDefault の安全な使い方

Single は「ちょうど1件だけあるはず」というときに使います。

var e = employees.Single(x => x.No == 1);
C#

0件でも2件以上でも例外になります。
「1件だけであるべき」という業務ルールをコードに刻むためのメソッドです。

「0件もあり得るが、2件以上はおかしい」というときは SingleOrDefault を使います。

var e = employees.SingleOrDefault(x => x.No == 1); // 0件なら null、2件以上なら例外
C#

ここでの重要ポイントは、「Single 系は“件数の正しさ”をチェックするための道具」だということです。
安全取得というより、「データ不整合を早期にあぶり出す」ために使います。

ElementAtOrDefault でインデックスの安全取得

LINQ には、インデックス版の安全取得もあります。

var list = new List<string> { "A", "B", "C" };

string? v1 = list.ElementAtOrDefault(1); // "B"
string? v9 = list.ElementAtOrDefault(9); // null
C#

list[9] なら例外ですが、ElementAtOrDefault(9) なら既定値(参照型なら null)になります。


Dictionary の安全取得:TryGetValue と「デフォルト値」

TryGetValue で「ないかもしれない」を前提に書く

Dictionary の安全取得の主役は TryGetValue です。

var map = new Dictionary<string, string>
{
    ["A"] = "りんご",
    ["B"] = "バナナ",
};

if (map.TryGetValue("A", out var name))
{
    Console.WriteLine(name);
}
else
{
    Console.WriteLine("見つかりません");
}
C#

ここでの重要ポイントは、「例外ではなく“分岐”で制御している」ことです。
dict[key] は「ある前提」、TryGetValue は「ないかもしれない前提」です。

「なかったらデフォルト値」を返すユーティリティ

毎回 if を書くのが面倒なら、拡張メソッドで「安全取得+デフォルト値」をまとめてもよいです。

public static class DictionaryExtensions
{
    public static TValue? GetOrDefault<TKey, TValue>(
        this IDictionary<TKey, TValue> source,
        TKey key,
        TValue? defaultValue = default)
        where TKey : notnull
    {
        return source.TryGetValue(key, out var value)
            ? value
            : defaultValue;
    }
}
C#

使い方はこうです。

var name = map.GetOrDefault("X", "不明"); // 見つからなければ "不明"
C#

ここでの重要ポイントは、「“なかったときどうするか”をメソッドの引数で決めている」ことです。
呼び出し側の if が減り、意図が読みやすくなります。


null 安全取得:?. と ?? の組み合わせ

コレクション自体が null のとき

業務コードでは、List<T>?IEnumerable<T>? が null のこともよくあります。

List<string>? names = GetNamesOrNull();
C#

このとき、いきなり names.FirstOrDefault() と書くと NullReferenceException です。

安全に書くなら、こうです。

string? first = names?.FirstOrDefault();
C#

names が null なら、first も null になります。

「null なら空扱い」にする

「null =データなし」とみなして、空列として扱いたいことも多いです。

var safeNames = names ?? Enumerable.Empty<string>();

string? first = safeNames.FirstOrDefault();
C#

ここでの重要ポイントは、「null を“例外の原因”にするか、“0件扱い”にするかを設計で決める」ことです。
多くの集計・一覧処理では、「null なら空」としてしまうほうが扱いやすくなります。


実務で意識してほしい「安全取得」の設計視点

1件もないのは「普通」か「異常」か

安全取得を考えるとき、必ず自分に問いかけてほしいのがこれです。

  • 0件なのは、業務的に普通にあり得るか?
  • それとも、0件はおかしい(データ不整合)か?

普通にあり得るなら、FirstOrDefaultTryGetValue で「なかったときの振る舞い」を書く。
おかしいなら、あえて FirstSingle で例外にして、バグとして気づけるようにする。

ここを曖昧にすると、「たまたま0件だっただけで落ちる」か、「おかしなデータでも気づかない」かのどちらかになります。

「なかったときどうするか」をコードに刻む

安全取得は、「なかったときの方針」をコードに埋め込む作業です。

  • null を返す(呼び出し側で判断させる)
  • デフォルト値を返す(0、””、”不明” など)
  • ログを出してスキップする
  • 例外にして止める

どれを選ぶかは業務次第ですが、「何も考えずに First()dict[key] を書く」のは、ほぼ確実に後で痛みます。


まとめ:「安全取得ユーティリティ」は“現場で落ちないコード”の土台

安全取得の本質は、

「データが“ないかもしれない”現実を、ちゃんとコードに反映する」

ことです。

押さえておきたい道具は、

  • インデックス:範囲チェック or ElementAtOrDefault
  • LINQ:FirstOrDefault / SingleOrDefault(0件を許容するかどうか)
  • Dictionary:TryGetValueGetOrDefault 的なユーティリティ
  • null:?.???? Enumerable.Empty<T>()

そして何より大事なのは、

「0件/キーなし/null は、業務的にどう扱うべきか?」

を先に決めてからコードを書くことです。

ここが腹に落ちると、「とりあえず取ってみて、落ちたら直す」スタイルから卒業して、
“最初から落ちない設計で書く”エンジニア側の思考に、一段レベルアップできます。

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