どうでもいいコードの断片
例えば以下のようなExcel、Word、PowerPointのファイル名の配列があるとする。
var fileNames = new [] { "200.xls", "100.doc", "300.ppt", "500.xls", "400.ppt", "600.ppt" };
この配列から格拡張子毎にリストに分けたいとする。Outputとして欲しいのは以下の三つのリスト。
var xlsFiles = new List<string>(); var docFiles = new List<string>(); var pptFiles = new List<string>();
この処理を書くとき、素直に書けば以下のようなif/else文になるだろう。
foreach(var f in fileNames) { string extension = Path.GetExtension(f).ToLower(); if(extension == ".xls") xlsFiles.Add(f); else if(extension == ".doc") docFiles.Add(f); else if(extension == ".ppt") pptFiles.Add(f); }
ディクショナリを使えば以下のように書くこともできる。
var map = new Dictionary<string, List<string>>( StringComparer.Create(CultureInfo.CurrentCulture, true) ); map.Add(".xls", xlsFiles); map.Add(".doc", docFiles); map.Add(".ppt", pptFiles); foreach(var f in fileNames) { string extension = Path.GetExtension(f); if(map.ContainsKey(extension)) map[extension].Add(f); }
こういった条件分岐の処理はよくあるが、常々もっとスマートに短く(なんならワンライナーぐらいの勢い)書くことができないかと思っていた。まぁ、こんなところ悩むような所ではないかもしれないけど、なんか気になる。
で色々書いてみた。
まずはLINQで提供されるwhereの拡張メソッド版を使ってフィルタリングした結果をリストに追加する。
xlsFiles.AddRange( fileNames.Where(f => f.EndsWith(".xls", StringComparison.CurrentCultureIgnoreCase)) ); docFiles.AddRange( fileNames.Where(f => f.EndsWith(".doc", StringComparison.CurrentCultureIgnoreCase)) ); pptFiles.AddRange( fileNames.Where(f => f.EndsWith(".ppt", StringComparison.CurrentCultureIgnoreCase)) );
大文字小文字を区別しなくていいならもっと短くなるのに。
次は共通の処理を抜き出して匿名メソッドにして、それを呼び出す方式。
Action<List<string>, string> func = (list, extension) => { list.AddRange( fileNames.Where(f => f.EndsWith(extension, StringComparison.CurrentCultureIgnoreCase)) ); }; func(xlsFiles, ".xls"); func(docFiles, ".doc"); func(pptFiles, ".ppt");
最後、LINQのGroupByを使う方式。
List<string>[] result = fileNames.GroupBy(s => Path.GetExtension(s).ToLower(), (key, list) => new List<string>(list)).ToArray();
一行で分ける事ができたけど、戻り値の配列のどれがどの拡張子の配列かわからないのが欠点。結局それを判断するために条件分岐が必要になる。
使いどころのないとんでもコードばかりだったけど、最後のGroupByは使えそうなので覚えとこ。