Microsoft.VisualBasic.FileIO名前空間にTextFieldParserというCSVファイルや固定長(ホストから送られてくるCOBOLで作成されてくるシーケンシャルファイルですね)レコードを読み込む便利なクラスがあるので、TextFieldParserを利用したCSVReaderを作成したので、メモとして掲載します。

CSVファイルの仕様(原文)
- http://www.ietf.org/rfc/rfc4180.txt 
  CSVファイルの一般的書式 (RFC4180 日本語訳) 
- http://www.kasai.fm/wiki/rfc4180jp

動作確認環境

  • Windows Vista
  • 開発環境 Visual Studio 2008 Professional
  • .NET 2.0以上

1. CSVReaderクラスの定義

定義は以下の用にしました。

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualBasic.FileIO;
using System.IO;

namespace CSVReaderWriter
{
    /// <summary>
    /// CSV Reader. Microsoft.VisualBasic.FileIO.TextFieldParser
    /// のラッパー
    /// Microsoft.VisualBasic.FileIO名前空間を利用するにはMicrosoft.VisualBasic.dllへの
    /// 参照が必要です。
    /// </summary>
    class CSVReader : IDisposable
    {
        TextFieldParser _parser = null;

        #region コンストラクタ
        public CSVReader(Stream stream)
        {
            _parser = new TextFieldParser(stream);
            Initialize();
        }
        public CSVReader(string path)
        {
            _parser = new TextFieldParser(path);
            Initialize();
        }
        public CSVReader(Stream stream, Encoding defaultEncoding)
        {
            _parser = new TextFieldParser(stream, defaultEncoding);
            Initialize();
        }
        public CSVReader(string path, Encoding defaultEncoding)
        {
            _parser = new TextFieldParser(path, defaultEncoding);
            Initialize();
        }
        public CSVReader(TextReader reader)
        {
            _parser = new TextFieldParser(reader);
            Initialize();
        }
        #endregion

        #region プロパティ
        /// <summary>
        /// コメントトークンを定義します。コメントトークン
        /// である文字列を先頭の行に置くと、その行がコメント
        /// であり、パーサーによって無視されることを示します。
        /// </summary>
        public string[] CommentTokens
        {
            get { return _parser.CommentTokens; }
            set { _parser.CommentTokens = value; }
        }
        /// <summary>
        /// テキストファイルの区切り記号を定義します。
        /// </summary>
        public string[] Delimiters
        {
            get { return _parser.Delimiters; }
            set { _parser.Delimiters = value; }
        }
        /// <summary>
        /// ファイルの終端か否かを返します。
        /// 現在のカーソル位置と、ファイルの終端との間に、
        /// 空行またはコメント行以外のデータが存在しない場合
        /// Trueを返します。
        /// </summary>
        public bool EndOfData
        {
            get { return _parser.EndOfData; }
        }
        /// <summary>
        /// 最近発生した MalformedLineException 例外の原因となった行を返します。
        /// </summary>
        public string ErrorLine
        {
            get { return _parser.ErrorLine; }
        }
        /// <summary>
        /// 最近 MalformedLineException 例外が発生した行の番号(0ベース)を返します。
        /// </summary>
        public long ErrorLineNumber
        {
            get { return _parser.ErrorLineNumber - 1; }
        }
        /// <summary>
        /// 解析するテキスト ファイルの各列の幅を表します。
        /// </summary>
        public int[] FieldWidths
        {
            get { return _parser.FieldWidths; }
            set { _parser.FieldWidths = value; }
        }
        /// <summary>
        /// 区切り形式のファイルを解析する際、フィールドが引用符で
        /// 囲まれているかどうかを示します
        /// </summary>
        public bool HasFieldsEnclosedInQuotas
        {
            get { return _parser.HasFieldsEnclosedInQuotes; }
            set { _parser.HasFieldsEnclosedInQuotes = value; }
        }
        /// <summary>
        /// 現在の行番号を返します。ストリームから取り出す文字がなくなった
        /// 場合は負の値を返します。
        /// </summary>
        public long LineNumber
        {
            get { return _parser.LineNumber - 1; }
        }
        /// <summary>
        /// フィールド値から前後の空白をトリムするかどうかを示します。
        /// </summary>
        public bool TrimWhiteSpace
        {
            get { return _parser.TrimWhiteSpace; }
            set { _parser.TrimWhiteSpace = value; }
        }
        /// <summary>
        /// フィールドが区切られているかのフラグ
        /// </summary>
        public bool IsFieldTypeDelimited
        {
            get { return _parser.TextFieldType == FieldType.Delimited; }
        }
        /// <summary>
        /// フィールドが固定長かのフラグ
        /// </summary>
        public bool IsFieldTypeFixed
        {
            get { return _parser.TextFieldType == FieldType.FixedWidth; }
        }

        #endregion

        #region メソッド定義
        /// <summary>
        /// パーサーを閉じる
        /// </summary>
        public void Close()
        {
            if (_parser != null) _parser.Close();
        }
        /// <summary>
        /// ストリームから文字をnumberOfChars文字取りだす。
        /// 取り出した文字はストリームから削除されません。
        /// </summary>
        /// <param name="numberOfChars"></param>
        /// <returns></returns>
        public string PeekChars(int numberOfChars)
        {
            return _parser.PeekChars(numberOfChars);
        }
        /// <summary>
        /// フィールドに分解された1行を取り出します。
        /// </summary>
        /// <returns></returns>
        public string[] ReadFields()
        {
            return _parser.ReadFields();
        }
        /// <summary>
        /// 1行文字列として読み出します。
        /// </summary>
        /// <returns></returns>
        public string ReadLine()
        {
            return _parser.ReadLine();
        }
        /// <summary>
        /// 現在のカーソル位置からStreamから全ての文字列を取り出します。
        /// </summary>
        /// <returns></returns>
        public string ReadToEnd()
        {
            return _parser.ReadToEnd();
        }
        /// <summary>
        /// 区切り文字を設定する
        /// </summary>
        /// <param name="delimiters"></param>
        public void SetDelimiters(string[] delimiters)
        {
            _parser.SetDelimiters(delimiters);
        }
        /// <summary>
        /// 固定長フィールドの場合のフィールド幅を設定する。
        /// </summary>
        /// <param name="fieldWidths"></param>
        public void SetFieldWidth(int[] fieldWidths)
        {
            _parser.SetFieldWidths(fieldWidths);
        }
        /// <summary>
        /// フィールドタイプを区切り文字に変更する
        /// </summary>
        public void SetFieldTypeDelimited()
        {
            _parser.TextFieldType = FieldType.Delimited;
        }
        /// <summary>
        /// フィールドタイプを固定幅に変更する
        /// </summary>
        public void SetFieldTypeFixed()
        {
            _parser.TextFieldType = FieldType.FixedWidth;
        }
        /// <summary>
        /// 初期化処理。
        /// コンストラクタから呼び出され、初期設定を行います。
        /// ファイルのデリミタをタブ,フィールドタイプを非固定長
        /// TrimWhiteSpace=Falseとして初期化します。
        /// </summary>
        protected virtual void Initialize()
        {
            SetDelimiters(new string[] { "\t" });
            SetFieldTypeDelimited();
            TrimWhiteSpace = false;
        }
        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            Close();
            if (_parser != null) _parser.Dispose();
        }
        void IDisposable.Dispose()
        {
            this.Dispose();
        }

        #endregion
    }
}

2. 動作サンプルプログラム

前回作成したCSVWriterも使った動作確認プログラムを掲載します。

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

namespace CSVReaderWriter
{
    class Program
    {
        static void Main(string[] args)
        {
            DataTable tbl = new DataTable();
            tbl.Columns.Add("col1");
            tbl.Columns.Add("col2");
            tbl.LoadDataRow(new object[] { "te\nst1", "test2" }, false);
            tbl.LoadDataRow(new object[] { "test3", "test4" }, false);
            tbl.AcceptChanges();

            using (CSVWriter w = new CSVWriter(@"D:\temp\test.csv"))
            {
                w.Separator = ",";
                w.WriteOnce(tbl);
            }
            using (CSVReader r = new CSVReader(@"D:\temp\test.csv"))
            {
                r.Delimiters = new string[] { "," };
                while (!r.EndOfData)
                {
                    Console.Write("Line {0}:", r.LineNumber);
                    string[] fields = r.ReadFields();
                    foreach (string field in fields)
                    {
                        Console.Write(field);
                    }
                    Console.WriteLine();
                }
            }
            Console.ReadLine();
        }
    }
}

説明は以上です。

誤り、指摘等があればご連絡ください。