Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

最近、ちょっとしたシミュレーションなどで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()を満たすためだけにコンストラクタを定義させるのは面倒だろうということで、仕方なくこのような形になりました。何かいいアイデアはありませんか?

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした