LoginSignup
19
24

More than 5 years have passed since last update.

LINQの活用を踏まえたCSVReaderクラスを作ってみた

Last updated at Posted at 2015-01-17

最近、ちょっとしたシミュレーションなどでCSVファイルを読み込むことが多いので、CSVReaderクラスを作ってみました。
初投稿ですが、色々と厳しい意見をお願いします。

まず、LineDataという抽象クラスを定義します。
これは、入力したいCSVの1行にどのようなデータが入っているかを定義しておくクラスの基本クラスです。

LineData.cs
namespace CSV
{
    public abstract class LineData
    {
        public LineData() { }

        public abstract void SetDataFrom(string[] s);
    }
}

次にCSVReaderクラスですが、filepathからCSVファイルを読み込んでLineDataを放出し続けます。このため、LineDataの派生型を型引数とするジェネリックにしてあり、IEnumerableも実装しています。

CSVReader.cs
using System;
using System.Collections.Generic;
using System.IO;

namespace CSV
{
    public class CSVReader<T> : IEnumerable<T>, IDisposable
        where T : LineData, new()
    {
        private StreamReader reader;
        private string filepath;
        private bool skip;

        public CSVReader(string filepath, bool skip = true)
        {
            if (!filepath.EndsWith(".csv", StringComparison.CurrentCultureIgnoreCase))
            {
                throw new FormatException("拡張子が.csvではないファイル名が指定されました。");
            }
            this.filepath = filepath;
            this.skip = skip;
            reader = new StreamReader(filepath);
            if (skip) reader.ReadLine();
        }

        public void Dispose()
        {
            reader.Dispose();
        }

        public IEnumerator<T> GetEnumerator()
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                var data = new T();
                data.SetDataFrom(line.Split(','));
                yield return data;
            }
            reader = new StreamReader(filepath);
            if (skip) reader.ReadLine();
            yield break;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}

利用側では、まずLineDataを派生させSetDataFromを実装してもらいます。

実装例
    class IntIntLine : LineData
    {
        public int data1,data2;

        public override void SetDataFrom(string[] s)
        {
            data1 = int.Parse(s[0]);
            data2 = int.Parse(s[1]);
        }
    }

そして、この派生型を型引数に用いてCSVReaderをusingするだけです。

利用例
    class Program
    {
        static void Main()
        {
            using (var reader = new CSVReader<IntIntLine>(@"example.csv"))
            {
                var s = reader.Where(i => i.data1 > 0).Select(i => i.data2).Sum();
                Console.WriteLine(s);
            }
            return;
        }
    }

このCSVReaderの利点としては、IEnumerableを実装しているため、foreachやLinqが使い放題になることでしょう。またLinqの遅延評価を生かし、メモリを節約しながらCSV全体を計算に使用できます。

ちなみにLineDataをインターフェースにしていないのは、常に制約条件new()を満たすためだけにコンストラクタを定義させるのは面倒だろうということで、仕方なくこのような形になりました。何かいいアイデアはありませんか?

19
24
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
24