C#を使用してアプリケーションを開発する際、ファイル操作や文字列検索において「特定のパターンに一致するものを抽出したい」という場面は非常に多く発生します。
その際に役立つのが、ワイルドカードを用いたパターンマッチングです。
ワイルドカードとは、不特定の文字や文字列を表す特別な記号のことで、一般的にはアスタリスク * や疑問符 ? が使用されます。
C#自体にはワイルドカードを直接評価する「記号」としての言語機能は限定的ですが、.NETのクラスライブラリや正規表現、最新のパターンマッチング機能を組み合わせることで、高度な検索ロジックを簡潔に記述することが可能です。
本記事では、ファイル検索から文字列判定、さらには最新のC#における高度なパターンマッチングまで、ワイルドカードを活用する方法を徹底的に解説します。
ファイル操作におけるワイルドカードの活用
C#で最もワイルドカードが頻繁に使われる場面は、ファイルシステムの操作です。
特定の拡張子を持つファイルを探したり、特定の命名規則に従ったディレクトリを取得したりする際に、標準ライブラリのメソッドがワイルドカードをサポートしています。
Directory.GetFilesメソッドによる検索
System.IO 名前空間に含まれる Directory.GetFiles メソッドは、引数として検索パターンを受け取ることができます。
ここで使用できるワイルドカードは主に以下の2種類です。
- アスタリスク (*):0文字以上の任意の文字列に一致します。
- 疑問符 (?):正確に1文字の任意の文字に一致します。
以下のサンプルコードは、特定のディレクトリからテキストファイルを検索する例です。
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 を使用して、検索の挙動をより詳細に制御できるようになりました。
これにより、サブディレクトリの再帰検索や、大文字・小文字の区別などを柔軟に設定可能です。
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.EnumerateFiles と EnumerationOptions を組み合わせることで、パフォーマンスと精度の高いファイル検索が実現できます。
文字列判定におけるワイルドカードの実装
ファイル検索以外で「文字列が特定のワイルドカードパターンに一致するか」を判定したい場合、C#の標準的な文字列メソッド(StartsWithやEndsWith)だけでは不十分なことがあります。
このセクションでは、文字列に対してワイルドカードマッチングを実現する3つの手法を紹介します。
1. Regex(正規表現)への変換による判定
C#で最も汎用的かつ強力な方法は、ワイルドカードパターンを正規表現(Regular Expressions)に変換して判定することです。
ワイルドカードの * は正規表現の .* に、? は . に対応させます。
以下のコードは、ワイルドカード文字列を正規表現に変換してマッチングを行うユーティリティ関数の例です。
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#プロジェクトに参照を追加することで利用可能です。
// 参照設定で 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 のように、ディレクトリ階層を跨ぐワイルドカードを処理できるのが最大の特徴です。
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以降)では、コレクションや配列に対して「スライスパターン」と呼ばれるワイルドカードに近い記法が導入されました。
これはファイルシステムのワイルドカードとは異なりますが、データ構造のパターンマッチングにおいて非常に強力です。
.. (ダブルドット)を使用することで、任意の要素の並びにマッチさせることができます。
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 などを組み合わせることで、直感的なフィルタリングが可能です。
文字列メソッドによる疑似ワイルドカード
最も軽量なのは、StartsWith、EndsWith、Contains を組み合わせることです。
| ワイルドカード形式 | LINQ / Stringメソッド |
|---|---|
prefix* | s.StartsWith("prefix") |
*suffix | s.EndsWith("suffix") |
*keyword* | s.Contains("keyword") |
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 句に変換されるワイルドカード検索が実行されます。
// 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.GetFilesやEnumerationOptionsを活用するのが最も標準的です。 - 文字列のパターン判定であれば、ワイルドカードを正規表現に変換して
Regex.IsMatchで評価する方法が最も柔軟です。 - ディレクトリ階層を含む複雑な検索には、
FileSystemGlobbingライブラリが強力な武器となります。 - 最新のC# 11以降であれば、リストパターンによる洗練されたマッチングも検討すべき選択肢です。
ワイルドカードは、ユーザーにとって直感的で使いやすい検索手段を提供します。
本記事で紹介した手法を適切に使い分けることで、堅牢かつ効率的なアプリケーション開発を実現してください。
特に、パフォーマンスとセキュリティの両立を意識することが、プロフェッショナルなコードへの第一歩となります。






