Mainly Devel Notes

Twitter, GitHub, StackOverflow: @ovrmrw (short hand of "overmorrow" that means the day after tomorrow)

Files To Lines - LINQ、Aggregate、拡張メソッド、複数のファイルからのデータ読み取りを1行でこなす。

LINQ(.NET)のAggregateを使ってコレクションの中身をすり替えながら流れるように処理を記述できることに気付いた。

それで今回やりたいのは、

複数のファイルからデータを読み取って1つのListにまとめたい。それも1行で書きたい。

名前を付けるとしたら、Files To Lines。あんまりセンスない。

さて、ファイルといってもこの場合はCSVファイルのようなものを指します。
標準のLINQ機能だけで書くとやや冗長になるので、Comment,ForEachThenReturn,AddRangeThenReturnを拡張メソッドとしてMyExtensionsクラスに定義します。
このへんは好みの問題ですけどね。

  • Comment … 文字通り、文字列をコンソールに出力するだけ。ただし処理を途切れさせない。
  • ForEachThenReturnListクラスのForEachメソッドは戻り値がvoidなのでそこで処理が途切れてしまう。だから途切れないようにする。
  • AddRangeThenReturnListクラスのAddRangeメソッドは戻り値がvoidなので以下略

ディレクトリの一覧を取得し、そこから必要なファイルだけを抽出し、File.ReadAllLinesメソッドでファイルの中身を読み取る、というようなことがLINQを使って1行でできます。(何度も改行はしていますが)

畳み込むだけでなく展開もできる、これがAggregateの底力ですね。


Program.cs

using System.IO;

class Program
{
    static void Main(string[] args)
    {
        var currentDir = Directory.GetCurrentDirectory();
        var partialNameOfTargetFile = "hoge";
        var listOfLines = Directory.GetDirectories(currentDir, "*", SearchOption.AllDirectories)
            .Comment("********** The contents of the collection are directory names.")
            .ForEachThenReturn(i => Console.WriteLine("Directory: " + i))
            .Aggregate(new List<string>(), (a, i) => a.AddRangeThenReturn(Directory.GetFiles(i)))
            .Where(w => w.Contains(partialNameOfTargetFile))
            .Comment("********** The contents of the collection are file names picked up from the directories.")
            .ForEachThenReturn(i => Console.WriteLine("File: " + i))
            .Aggregate(new List<string>(), (a, i) => a.AddRangeThenReturn(File.ReadAllLines(i, Encoding.UTF8)))
            .Comment("********** The contents of the collection are lines read from the files.")
            .ForEachThenReturn(i => Console.WriteLine("Line: " + i))
            ;
    }
}

MyExtensions.cs

public static class MyExtensions
{
    public static IEnumerable<T> ForEachThenReturn<T>(this IEnumerable<T> obj, Action<T> action)
    {
        var temp = obj;
        temp.ToList().ForEach(action);
        return obj;
    }

    public static List<T> AddRangeThenReturn<T>(this List<T> obj, IEnumerable<T> collection)
    {
        obj.AddRange(collection);
        return obj;
    }

    public static T Comment<T>(this T obj, string comment)
    {
        Console.WriteLine(comment);
        return obj;
    }
}