• システム開発に関わる内容をざっくりと書いていく

C# KeyNotFoundException 発生原因と対策

Dictionary から値を引いたら KeyNotFoundException(「指定されたキーはディレクトリ内に存在しませんでした」)で落ちる。Null 系の例外と違い、これは「器はあるがキーが無い」状態で、原因はキーの登録漏れか、キー自体の作り間違いのどちらかにほぼ絞れる。

先に結論

  • 発生条件はシンプルで、存在しないキーでインデクサ(dict[key])を引いたときだけ
  • 取り方が分かれる: 在る前提ならインデクサ、無いかもしれないなら TryGetValue を使う
  • バグの多くは「登録したつもり」: 初期化漏れ、キー生成の不整合(大文字小文字・型違い)に潜む
  • ContainsKey で確認してから引く書き方は、検索が2回走るので原則 TryGetValue に寄せる

よくある発生パターン

  • 初期化・登録処理を通っていないキーで引く(条件分岐で登録がスキップされる)
  • キーの作りが登録側と参照側で違う: 文字列の大文字小文字、末尾スペース、数値キーと文字列キーの取り違え
  • 外部入力(JSON・CSV・設定ファイル)を Dictionary 化した前提が崩れている
  • 列挙中にキーが変わる設計で、古いキーのまま引き続ける

安全な引き方

「無ければ既定値・分岐」で済むなら TryGetValue が基本形。1回の検索で有無と値が取れる。

if (dict.TryGetValue(key, out var value))
{
    // 在ったときの処理
}

// 無ければ既定値でよい場合
var v = dict.GetValueOrDefault(key, defaultValue);
  • ContainsKey + インデクサの組み合わせは検索2回分。意図が無ければ置き換える
  • 「必ず在るべきキー」なら、引く側で握りつぶさず、登録側の漏れを直すのが本筋
  • 設定値のようなものは起動時にまとめて検証し、実行時に落ちる形を避ける

キーが合わない系の落とし穴

  • 文字列キーは大文字小文字を区別する(StringComparer.OrdinalIgnoreCase を渡した Dictionary は別)
  • 独自クラスをキーにする場合、Equals / GetHashCode の実装次第で「同じはずのキー」が一致しない
  • 浮動小数点数や日時をキーにすると、表現の揺れで一致しない事故が起きやすい

予防

  • キーの生成ロジックを1箇所に寄せ、登録側と参照側で別々に組み立てない
  • 外部入力由来の Dictionary は、引く前に「想定キーが揃っているか」を検証する
  • テストでは「キーが無いケース」を必ず通す。正常系だけだとこの例外は潜伏する

ざっくりまとめ

  • KeyNotFoundException は「キーが無い」ただそれだけ。在る前提か無い前提かをコードで選ぶ
  • 無い前提なら TryGetValue / GetValueOrDefault、在る前提なら登録漏れを直す
  • キーの不整合(大小文字・型・独自クラスの等価性)が隠れ原因の定番

関連記事