新しもの好きプログラマの耳より情報ブログ

仕事でもあるプログラミングについて役に立ちそうな情報を発信していこうというブログです。役に立たなそうな情報はfacebookで。

C#でのファイルパスの重複排除(大小文字無視)は、Dictionaryなどのコレクションにやらせると便利

概要

Windowsのファイルパスなどで、大文字小文字を無視して文字列比較をしたい場合があります。C#では、単純な比較もできますし、ソートと一致判定を伴うコレクション(HashSet,Dictionaryなど)でもそうした比較を行うことができます。ただし比較方式にはいくつかの選択肢があります。これらについて、意外とちゃんと使えていないコードを見かけるので、小ネタですが記事を書きました。

結論

最初に結果だけ言うと、このように書けば、大文字小文字を無視して比較し重複排除するリストができます。これを見て「常識だろ」と思う人も多いと思いますが、意外に「何それ?!」という人もいます。

HashSet<string> filePaths = new(StringComparer.OrdinalIgnoreCase);
var add = @"C:\FilePath1"; //追加するファイルパス
filePaths.Add(add);

よく見るコードとその改善

使えていないというのは、.NETがやってくれることを次の例のように自力でやってしまうということです。意外に、次のようなコードはよく見かけます。(そういうコードではLINQも使っていないことが多いですが、この話と無関係なので使うサンプルにします)

使えていない例

List<string> filePaths = new();
var add = @"C:\FilePath1"; //追加するファイルパス
if (filePaths.All(d => d.ToUpper() != add.ToUpper()))
{
    filePaths.Add(add);
}

大小文字無視の比較のためにToUpper()を使い、既存のListに含まれていない場合はListへ追加しています。

もちろんこのコードでも動くとは思いますが、次のように.NETにやってもらった方が保守性も、おそらくは性能も上がります。

改善1:大小文字無視の比較はEqualsメソッドで

ToUpper()で頑張らなくても、文字列のEqualsメソッドは、大小文字無視の比較ができます。

List<string> filePaths = new();
var add = @"C:\FilePath1"; //追加するファイルパス
if (filePaths.All(d => d.Equals(add, StringComparison.OrdinalIgnoreCase)))
{
    filePaths.Add(add);
}

改善2:コレクションの重複は、コレクションに判定させる

重複排除したリストを作りたいなら、HashSetが使えます。HashSetは、コンストラクタに指定することで大小文字無視の比較ができます。

HashSet<string> filePaths = new(StringComparer.OrdinalIgnoreCase);
var add = @"C:\FilePath1"; //追加するファイルパス
filePaths.Add(add);

Dictionaryでも使えます

Dictionaryのキーの判定にも同じ方法を使えます。それ以外にもConcurrentDictionary等のソート・一致判定を伴うコレクションなら、おそらく全て使えます。

Dictionary<string, int> filePathAndValues = new(StringComparer.OrdinalIgnoreCase);
//追加するファイルパスと値
var addFilePath = @"C:\FilePath1";
int addValue = 1;
filePathAndValues.Add(addFilePath, addValue);

StringComparerの選び方

StringComparerというのが出てきましたが、大小文字無視(IgnoreCase)のものだけでも3種類あります。どれを使ったら良いでしょう。これも割と、書く人によってバラバラだったりします。

Windowsのファイルパスの判定が目的なら、StringComparer.OrdinalIgnoreCaseを使いましょう。大文字小文字の差を無視した上でのシンプルなバイナリ比較なので、ファイルパスのような一致判定には適しています。

悩む人が多かったのか、Learnにまとまっていました。ここでもやはり、ファイルパスなどはOrdinalIgnoreCaseが推奨されています。

.NET での文字列の比較に関するベスト プラクティス - .NET | Microsoft Learn

まとめ

単純なファイルパスの一致判定と重複排除だけの話ですが、ちゃんと.NETのライブラリを使うかどうかによって、コードの見やすさもおそらくは性能もだいぶ変わって来ます。こうしたことの積み重ねが、意外と保守性や性能に効いてくることもあると思うので、知らなかった人はぜひ使ってみてください。知っていた人は、周りに分かっていない人がいたらぜひ教えてあげて欲しいなと思います。