C#を用いたシステム開発において、何らかのデータやリソースが「存在するかどうか」を確認する処理は、プログラムの安全性と安定性を高めるために極めて重要です。

ファイル操作における例外防止、リストや配列の空チェック、そして実行時エラーの筆頭であるNullReferenceExceptionを回避するためのNull判定など、その用途は多岐にわたります。

現代のC#(C# 9.0以降や最新の.NET環境)では、以前よりも直感的かつ簡潔に記述できる構文が数多く導入されています。

本記事では、実務で頻用される「ファイル・ディレクトリ」「リスト・コレクション」「Null判定」「文字列」といったカテゴリごとに、最適な存在チェックの実装方法を詳しく解説します。

Null判定の基本と最新の書き方

C#において最も基本的な存在チェックは、オブジェクトがメモリ上に存在するかを確認する「Null判定」です。

かつては == null を使用するのが一般的でしたが、現在のC#ではより安全で読みやすい方法が推奨されています。

パターンマッチングを利用した判定

C# 7.0以降、特にC# 9.0で導入された is演算子によるパターンマッチング は、Null判定の標準的な手法となりました。

C#
// 従来の書き方
if (obj == null) 
{
    // nullの場合の処理
}

// 推奨される現代的な書き方(is null)
if (obj is null)
{
    // nullの場合の処理
}

// 否定の判定(is not null)
if (obj is not null)
{
    // オブジェクトが存在する場合の処理
}

is null を使用する最大のメリットは、演算子のオーバーロード(重定義)の影響を受けない点にあります。

特定のクラスで == 演算子が独自に実装されている場合、== null は意図しない挙動を示す可能性がありますが、is null は常に純粋なNullチェックを行います。

Null条件演算子とNull合体演算子

オブジェクトのプロパティにアクセスする際、そのオブジェクト自体がNullでないかを確認しながら処理を進めるには、Null条件演算子 (?.) が非常に便利です。

C#
// objがnullならnameもnullになる。エラーにはならない。
string? name = obj?.Name;

// Null合体演算子 (??) との組み合わせ
// objまたはNameがnullの場合に、デフォルト値として "Unknown" を代入
string displayName = obj?.Name ?? "Unknown";

// Null合体代入演算子 (??=) 
// dataがnullの場合のみ、新しいリストを生成して代入する
data ??= new List<string>();

これらの演算子を駆使することで、ネストされた深い階層にあるデータの存在チェックを一行で記述でき、コードの可読性が飛躍的に向上します。

ファイルとディレクトリの存在チェック

ファイルシステムを操作する際、対象のファイルやフォルダが存在しない状態でアクセスすると FileNotFoundException などの例外が発生します。

これを防ぐために、System.IO 名前空間のメソッドを使用します。

File.Exists と Directory.Exists

最も一般的な方法は、File.Exists および Directory.Exists メソッドを使用することです。

C#
using System.IO;

string filePath = @"C:\temp\sample.txt";
string dirPath = @"C:\temp\logs";

// ファイルの存在確認
if (File.Exists(filePath))
{
    Console.WriteLine("ファイルが見つかりました。");
}
else
{
    Console.WriteLine("ファイルが存在しません。");
}

// ディレクトリの存在確認
if (Directory.Exists(dirPath))
{
    Console.WriteLine("ディレクトリが存在します。");
}

注意点:アクセス権限と競合状態

File.Exists は、指定されたパスにファイルが存在し、かつ実行ユーザーに読み取り権限がある場合にのみ true を返します。

ファイルが存在していても権限がない場合は false になるため注意が必要です。

また、チェックした直後に別のプロセスによってファイルが削除される「Time-of-Check to Time-of-Use (TOCTOU)」という競合状態が発生する可能性があります。

厳密な堅牢性が求められる場合は、存在チェックだけに頼らず、try-catch による例外ハンドリングを併用するのがベストプラクティスです。

FileInfo と DirectoryInfo の活用

同じファイルに対して複数の属性(サイズや更新日時など)を確認する場合は、FileInfo クラスを使用すると効率的です。

C#
var fileInfo = new FileInfo(filePath);

if (fileInfo.Exists)
{
    // Existsプロパティでチェックしつつ、他の情報も利用可能
    Console.WriteLine($"サイズ: {fileInfo.Length} bytes");
}

リストやコレクションの存在チェック

リスト(List<T>)や配列などのコレクションに要素が含まれているかを確認する場合、単なるNullチェックだけでなく「中身が空ではないか」を判定する必要があります。

LINQの Any() メソッドによる判定

最も汎用的で推奨される方法は、LINQ(Language Integrated Query)の Any() メソッド を使用することです。

C#
using System.Linq;
using System.Collections.Generic;

List<string> items = new List<string> { "Apple", "Orange" };

// 要素が一つでも存在するか
if (items.Any())
{
    Console.WriteLine("リストには要素が含まれています。");
}

// 特定の条件に合致する要素が存在するか
if (items.Any(x => x == "Apple"))
{
    Console.WriteLine("Appleが見つかりました。");
}

Any() は、要素が一つ見つかった時点で処理を終了するため、非常に効率的です。

CountプロパティとAny()の使い分け

リストの要素数を取得する Count プロパティ(配列の場合は Length)を使用しても、存在チェックは可能です。

メソッド/プロパティ適した用途特徴
items.Any()IEnumerable一般可読性が高く、中身の種類を問わず使える。
items.Count > 0List<T>, Collectionメソッド呼び出しのオーバーヘッドがなく、わずかに高速。
items.Length > 0配列 (Array)配列の標準的な空チェック方法。

基本的には Any() を使用して「意図」を明確に表現し、極限までパフォーマンスを追求するループ内などでは Count を検討するというスタンスが良いでしょう。

Nullと空リストの同時判定

実務では、変数がNullである可能性と、リストが空である可能性の両方を考慮しなければなりません。

C#
List<string>? myItems = GetItems();

// Nullではなく、かつ要素が存在するか
if (myItems?.Any() == true)
{
    // 安全に処理を開始できる
}

この記述法は、myItems がNullなら全体がNullになり、false と判定されるため、非常に安全かつ簡潔です。

文字列の存在チェック(空文字・空白)

文字列の存在チェックでは、「変数がNullではないか」「空文字(””)ではないか」「半角スペースのみではないか」という3つの観点が必要です。

IsNullOrEmpty と IsNullOrWhiteSpace

C#の string クラスには、これらの判定をまとめて行う静的メソッドが用意されています。

C#
string? input = "  ";

// Nullまたは空文字 ("") のチェック
if (string.IsNullOrEmpty(input))
{
    // inputがnullまたは""の場合に実行
}

// Null、空文字、または「空白のみ」のチェック
if (string.IsNullOrWhiteSpace(input))
{
    // inputがnull, "", "   " などの場合に実行
    Console.WriteLine("有効な入力ではありません。");
}

ユーザー入力のバリデーション(妥当性確認)においては、スペースのみの入力を「存在しない」とみなしたいケースが多いため、string.IsNullOrWhiteSpace を使用するのが一般的です。

辞書(Dictionary)のキー存在チェック

Key-Valueペアを扱う Dictionary<TKey, TValue> では、存在しないキーを指定して値を取得しようとすると KeyNotFoundException が発生します。

これを回避する方法は2つあります。

ContainsKey と TryGetValue

C#
var dict = new Dictionary<int, string> { { 1, "C#" }, { 2, "VB" } };

// 1. ContainsKeyで存在を確認してからアクセス
if (dict.ContainsKey(1))
{
    string val = dict[1];
}

// 2. TryGetValueで一括処理(推奨)
if (dict.TryGetValue(1, out string? result))
{
    // キーが存在した場合、resultに値が格納される
    Console.WriteLine(result);
}

TryGetValue を使用すると、内部的なハッシュ計算を1回で済ませることができるため、ContainsKey でチェックした後にインデクサでアクセスするよりもパフォーマンスに優れています

高度な存在チェック:パターンマッチングの応用

最新のC#では、リストのパターンマッチングなど、より複雑な構造に対する存在チェックも可能になっています。

リストパターン (C# 11以降)

特定の要素構成であるかを確認しながら、存在チェックを行うことができます。

C#
int[] numbers = { 1, 2, 3 };

// 配列が特定の形(1で始まり、何らかの要素があり、3で終わる)かチェック
if (numbers is [1, _, 3])
{
    Console.WriteLine("条件に一致する配列が存在します。");
}

// 空の配列かどうか
if (numbers is [])
{
    Console.WriteLine("配列は空です。");
}

この記法は、単なる存在チェックを超えて、データの「形状」を保証する際に非常に強力なツールとなります。

実践的なプログラム例

ここまでの内容を統合し、実務でよくある「設定ファイルを読み込み、リスト化して、特定のデータが存在するか確認する」という一連の処理を実装したサンプルプログラムを紹介します。

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class Program
{
    static void Main()
    {
        string path = "config.txt";
        
        // 1. ファイルの存在チェック
        if (File.Exists(path))
        {
            // 2. 内容の読み込みと存在判定
            string[] lines = File.ReadAllLines(path);
            
            // 3. コレクションが空でないか判定
            if (lines is { Length: > 0 }) 
            {
                // 4. 特定のキーワードが含まれるかチェック (LINQ)
                if (lines.Any(l => l.Contains("AdminMode=true")))
                {
                    Console.WriteLine("管理者モードが有効な設定ファイルが存在します。");
                }
                else
                {
                    Console.WriteLine("設定ファイルはありますが、管理者設定は見つかりません。");
                }
            }
        }
        else
        {
            Console.WriteLine("設定ファイル自体が存在しません。");
        }
    }
}

実行結果の例(ファイルが存在し、キーワードがある場合)

管理者モードが有効な設定ファイルが存在します。

まとめ

C#での存在チェックは、対象となるデータの型やリソースの種類に応じて、最適な手法を選択することが重要です。

  • Null判定には、安全でモダンな is nullis not null を活用しましょう。
  • ファイル・ディレクトリには、File.ExistsDirectory.Exists を使用しますが、競合状態にも注意が必要です。
  • コレクションの空チェックには、可読性の高い LINQ の Any() が推奨されます。
  • 文字列は、空白文字も考慮できる string.IsNullOrWhiteSpace が便利です。
  • 辞書からの値取得は、パフォーマンス面から TryGetValue の使用を検討してください。

適切な存在チェックを行うことは、単にエラーを防ぐだけでなく、コードの意図を明確にし、保守性の高いプログラムを作り上げることにも繋がります。

本記事で紹介した最新の構文を取り入れ、より堅牢なC#プログラミングを実践していきましょう。