C#を使用してアプリケーションを開発する際、ファイル操作や文字列検索において「特定のパターンに一致するものを抽出したい」という場面は非常に多く発生します。

その際に役立つのが、ワイルドカードを用いたパターンマッチングです。

ワイルドカードとは、不特定の文字や文字列を表す特別な記号のことで、一般的にはアスタリスク * や疑問符 ? が使用されます。

C#自体にはワイルドカードを直接評価する「記号」としての言語機能は限定的ですが、.NETのクラスライブラリや正規表現、最新のパターンマッチング機能を組み合わせることで、高度な検索ロジックを簡潔に記述することが可能です。

本記事では、ファイル検索から文字列判定、さらには最新のC#における高度なパターンマッチングまで、ワイルドカードを活用する方法を徹底的に解説します。

ファイル操作におけるワイルドカードの活用

C#で最もワイルドカードが頻繁に使われる場面は、ファイルシステムの操作です。

特定の拡張子を持つファイルを探したり、特定の命名規則に従ったディレクトリを取得したりする際に、標準ライブラリのメソッドがワイルドカードをサポートしています。

Directory.GetFilesメソッドによる検索

System.IO 名前空間に含まれる Directory.GetFiles メソッドは、引数として検索パターンを受け取ることができます。

ここで使用できるワイルドカードは主に以下の2種類です。

  • アスタリスク (*):0文字以上の任意の文字列に一致します。
  • 疑問符 (?):正確に1文字の任意の文字に一致します。

以下のサンプルコードは、特定のディレクトリからテキストファイルを検索する例です。

C#
using System;
using System.IO;

class Program
{
    static void Main()
    {
        string targetDirectory = @"C:\SampleFiles";

        // ディレクトリが存在するか確認
        if (!Directory.Exists(targetDirectory))
        {
            Console.WriteLine("指定されたディレクトリが存在しません。");
            return;
        }

        // *.txt に一致するファイルを取得
        // 第2引数にワイルドカードパターンを指定
        string[] files = Directory.GetFiles(targetDirectory, "*.txt");

        Console.WriteLine($"{files.Length} 件のファイルが見つかりました:");
        foreach (string file in files)
        {
            Console.WriteLine(Path.GetFileName(file));
        }
    }
}

注意点として、Windowsのファイルシステムにおけるワイルドカードの挙動には歴史的な経緯による特殊な仕様があります。

例えば、*.txt と指定した場合、拡張子が .txt であるファイルだけでなく、.text.txts といった「.txtで始まる拡張子」がマッチすることがあります。

これを回避し、より厳密なマッチングを行いたい場合は、後述するLINQや正規表現を併用するのが一般的です。

EnumerationOptionsによる詳細な制御

モダンなC#(.NET Core 3.0以降)では、EnumerationOptions を使用して、検索の挙動をより詳細に制御できるようになりました。

これにより、サブディレクトリの再帰検索や、大文字・小文字の区別などを柔軟に設定可能です。

C#
using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = @"C:\Data";

        var options = new EnumerationOptions
        {
            // サブディレクトリも検索対象にする
            RecurseSubdirectories = true,
            // 隠しファイルやシステムファイルを除外する設定
            AttributesToSkip = FileAttributes.System | FileAttributes.Hidden,
            // 大文字・小文字を区別するかどうか
            MatchCasing = MatchCasing.CaseInsensitive,
            // ワイルドカードの解釈方法(厳密なマッチングを指定可能)
            MatchType = MatchType.Simple
        };

        // EnumerateFilesはGetFilesよりもメモリ効率が良い(逐次処理に適している)
        var files = Directory.EnumerateFiles(path, "report_202?.log", options);

        foreach (var file in files)
        {
            Console.WriteLine($"Found: {file}");
        }
    }
}

このように、Directory.EnumerateFilesEnumerationOptions を組み合わせることで、パフォーマンスと精度の高いファイル検索が実現できます。

文字列判定におけるワイルドカードの実装

ファイル検索以外で「文字列が特定のワイルドカードパターンに一致するか」を判定したい場合、C#の標準的な文字列メソッド(StartsWithやEndsWith)だけでは不十分なことがあります。

このセクションでは、文字列に対してワイルドカードマッチングを実現する3つの手法を紹介します。

1. Regex(正規表現)への変換による判定

C#で最も汎用的かつ強力な方法は、ワイルドカードパターンを正規表現(Regular Expressions)に変換して判定することです。

ワイルドカードの * は正規表現の .* に、?. に対応させます。

以下のコードは、ワイルドカード文字列を正規表現に変換してマッチングを行うユーティリティ関数の例です。

C#
using System;
using System.Text.RegularExpressions;

public static class WildcardHelper
{
    public static bool IsMatch(string input, string pattern)
    {
        // ワイルドカードを正規表現エスケープし、* と ? を変換する
        // 1. 特殊文字をエスケープ
        // 2. "*" を ".*" に置換
        // 3. "?" を "." に置換
        string regexPattern = "^" + Regex.Escape(pattern)
                                         .Replace(@"\*", ".*")
                                         .Replace(@"\?", ".") + "$";

        return Regex.IsMatch(input, regexPattern, RegexOptions.IgnoreCase);
    }
}

class Program
{
    static void Main()
    {
        string data = "Order_2023_Final_v1.docx";
        
        bool result1 = WildcardHelper.IsMatch(data, "Order_*_v?.docx");
        bool result2 = WildcardHelper.IsMatch(data, "*_Final_*");

        Console.WriteLine($"Result 1: {result1}"); // True
        Console.WriteLine($"Result 2: {result2}"); // True
    }
}

この手法のメリットは、複雑なパターンにも対応可能である点です。

例えば、特定の文字範囲を指定したい場合などは、正規表現の機能をそのまま拡張して取り入れることができます。

2. Microsoft.VisualBasic.CompilerServices の活用

意外と知られていない方法として、Visual Basic用のライブラリに含まれる Like 演算子の機能をC#から利用する方法があります。

VBの Like は非常に強力なワイルドカードサポートを持っており、C#プロジェクトに参照を追加することで利用可能です。

C#
// 参照設定で Microsoft.VisualBasic を追加する必要がある場合があります
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;

class Program
{
    static void Main()
    {
        string text = "ABC-123-XYZ";
        
        // VBの LikeOperator を使用
        // # は数字1文字にマッチするなど、独自の記法が使える
        bool isMatched = LikeOperator.LikeString(text, "ABC-#*-XYZ", CompareMethod.Text);

        Console.WriteLine($"VB Like Match: {isMatched}");
    }
}

ただし、この方法は.NETのクロスプラットフォーム開発において推奨されない場合があるため、純粋なC#の実装を好むプロジェクトでは避けたほうが無難です。

3. Microsoft.Extensions.FileSystemGlobbing の利用

より高度な「Glob(グラブ)パターン」を利用したい場合は、Microsoftが提供しているライブラリ Microsoft.Extensions.FileSystemGlobbing が最適です。

これは主にASP.NET Coreなどの構成ファイル読み込み等で使われていますが、単体のライブラリとして利用可能です。

**/*.cs のように、ディレクトリ階層を跨ぐワイルドカードを処理できるのが最大の特徴です。

C#
using Microsoft.Extensions.FileSystemGlobbing;
using System.IO;

class Program
{
    static void Main()
    {
        Matcher matcher = new Matcher();
        // 包含パターンの追加
        matcher.AddIncludePatterns(new[] { "src/**/*.cs", "tests/**/*.cs" });
        // 除外パターンの追加
        matcher.AddExclude("**/bin/**");

        // 実際のファイル構造に対してマッチングを実行
        PatternMatchingResult result = matcher.Execute(
            new DirectoryInfoWrapper(new DirectoryInfo(Directory.GetCurrentDirectory()))
        );

        foreach (var file in result.Files)
        {
            Console.WriteLine($"Matched file: {file.Path}");
        }
    }
}

C# 11以降の「リストパターン」による新しいマッチング

最新のC#(C# 11以降)では、コレクションや配列に対して「スライスパターン」と呼ばれるワイルドカードに近い記法が導入されました。

これはファイルシステムのワイルドカードとは異なりますが、データ構造のパターンマッチングにおいて非常に強力です。

.. (ダブルドット)を使用することで、任意の要素の並びにマッチさせることができます。

C#
using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };

        // リストパターンによるマッチング
        // [1, .., 5] は「1で始まり、途中に任意の要素があり、5で終わる」という意味
        if (numbers is [1, .. var middle, 5])
        {
            Console.WriteLine("パターンに一致しました!");
            Console.WriteLine($"中間の要素数: {middle.Length}");
        }

        string[] words = { "Apple", "Banana", "Orange", "Grape" };

        // 特定の条件を含む配列の判定
        bool match = words is ["Apple", ..]; // Appleで始まる配列
        Console.WriteLine($"Apple starts: {match}");
    }
}

この機能は、CSVデータのパースや通信プロトコルの解析など、定型データの柔軟な判定に革命をもたらしました。

従来のループ処理やインデックス操作を大幅に削減でき、コードの可読性が飛躍的に向上します。

LINQとワイルドカードの組み合わせ

実務では、データベースから取得したリストや、メモリ上のコレクションに対してワイルドカード検索を行いたい場面が多いでしょう。

LINQと正規表現、あるいは string.Contains などを組み合わせることで、直感的なフィルタリングが可能です。

文字列メソッドによる疑似ワイルドカード

最も軽量なのは、StartsWithEndsWithContains を組み合わせることです。

ワイルドカード形式LINQ / Stringメソッド
prefix*s.StartsWith("prefix")
*suffixs.EndsWith("suffix")
*keyword*s.Contains("keyword")
C#
var items = new List<string> { "data.txt", "config.json", "setup.exe", "log.txt" };

// 拡張子が .txt のものを抽出
var textFiles = items.Where(s => s.EndsWith(".txt")).ToList();

// "config" を含むものを抽出
var configs = items.Where(s => s.Contains("config")).ToList();

EF Core(Entity Framework Core)でのワイルドカード

データベース検索においてワイルドカードを使用する場合、EF.Functions.Like を使用します。

これにより、SQLの LIKE 句に変換されるワイルドカード検索が実行されます。

C#
// EF Coreにおけるクエリ例
var users = context.Users
    .Where(u => EF.Functions.Like(u.Email, "%@gmail.com"))
    .ToList();

SQLの LIKE では %* に、_? に相当するため、C#標準のワイルドカードとは記法が異なる点に注意してください。

パフォーマンスとセキュリティの考慮事項

ワイルドカード検索を実装する際には、以下の2点に注意を払う必要があります。

1. パフォーマンスへの影響

大量のファイルを検索する場合、Directory.GetFiles は全結果を配列としてメモリに確保するため、ファイル数が多いとメモリを圧迫します。

一方、Directory.EnumerateFiles を使用すれば、列挙しながら1つずつ処理できるため、メモリ効率が大幅に向上します。

また、正規表現(Regex)をループ内で繰り返し生成するのは非効率です。

同じパターンを何度も使う場合は、Regex インスタンスを事前に生成し、RegexOptions.Compiled を指定することを検討してください。

2. セキュリティ(ReDoS攻撃)

ユーザーが入力した文字列をそのまま正規表現パターンとして利用する場合、正規表現サービス拒否攻撃(ReDoS)のリスクがあります。

悪意のある複雑なパターン(例:(a+)+$)を入力されると、マッチング処理が終わり、CPUを占有してしまう可能性があります。

ユーザー入力をワイルドカードとして受け入れる場合は、必ず以下のような対策を行ってください。

  • パターンの長さを制限する。
  • タイムアウト値を設定した正規表現オブジェクトを使用する。
  • 危険なメタ文字(ワイルドカード以外の正規表現記号)を適切にエスケープする。

まとめ

C#でワイルドカードを利用する方法は、用途に応じて多岐にわたります。

  • ファイル検索であれば、Directory.GetFilesEnumerationOptions を活用するのが最も標準的です。
  • 文字列のパターン判定であれば、ワイルドカードを正規表現に変換して Regex.IsMatch で評価する方法が最も柔軟です。
  • ディレクトリ階層を含む複雑な検索には、FileSystemGlobbing ライブラリが強力な武器となります。
  • 最新のC# 11以降であれば、リストパターンによる洗練されたマッチングも検討すべき選択肢です。

ワイルドカードは、ユーザーにとって直感的で使いやすい検索手段を提供します。

本記事で紹介した手法を適切に使い分けることで、堅牢かつ効率的なアプリケーション開発を実現してください。

特に、パフォーマンスとセキュリティの両立を意識することが、プロフェッショナルなコードへの第一歩となります。