最近はPythonの記事ばかり書いていたので、今回はC#を使ったCSV書き込みの方法について解説します。
とりあえず使いたい人はコピペで使ってください。
中身を理解してカスタマイズしたい方は、最後まで記事をお読みください。
CSVの書き込み方法
CSVは項目がカンマで区切られたテキストファイルです。
CSVの仕様を詳しくお知りになりたい方は、「2分で分かる!csvファイルの仕様と知っておきたいポイント」の記事を合わせてお読みください。
C#でテキストファイルを書き込み(保存)するには、方法として大きく2通りが用意されています。
1つは File.WriteAllLines() を使う方法、もう1つは StreamWriterを使う方法です。
いずれにせよ、これらを使うにはソースコードの冒頭に参照の記述 (using ~)が必要です。
次の1行を記述しておいてください。
1 |
using System.IO; |
File.WriteAllLines を使う方法
File.WriteAllLines は、第2引数で渡された文字列の配列(又はリスト)を一括で書き込んでくれる便利なメソッドです。
既に同じファイル名が存在する場合、上書きするか追加書き込みするかによって、ことなるメソッドを呼び分ける必要があります。
上書き ⇒ File.WriteAllLines(ファイル名,データ,文字コード)
追加 ⇒ File.AppendAllLines(ファイル名,データ,文字コード)
文字コードの引数は省略可能ですが、省略時は UTF-8 で書き込まれます。
CSVファイルをEXCELで開く場合は文字化けしますので、EXCELで開くことが前提なら、Encoding.GetEncoding("shift-jis") を指定する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using System.IO; #CSVデータをリストで用意 List<string> lines = new List<string>(); lines.Add("メーカー,製品名"); lines.Add("Dell,Inspiron"); lines.Add("富士通,FMV LIFEBOOK"); lines.Add("NEC,LAVIE Direct"); #リストの内容をファイル(CSV)に書き込む(上書き) File.WriteAllLines(@"d:\mydata.csv",lines,Encoding.GetEncoding("shift-jis")); #リストの内容をファイル(CSV)の末尾に追加する File.AppendAllLines(@"d:\mydata.csv", lines, Encoding.GetEncoding("shift-jis")); |
StreamWriterを使う方法
StreamWriterでファイルをオープンし、WriteLine でデータを1行づつファイルに書き込みます。
1行づつ書き込むタイミングで何らかの処理を行いたい場合、もしくは順次送られてくるデータに対して、メモリ内に蓄えずに都度ファイル出力したい場合に使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System.IO; #CSVデータをリストで用意 List<string> lines = new List<string>(); lines.Add("メーカー,製品名"); lines.Add("Dell,Inspiron"); lines.Add("富士通,FMV LIFEBOOK"); lines.Add("NEC,LAVIE Direct"); using (StreamWriter sw = new StreamWriter(@"d:\mydata2.csv", false, Encoding.GetEncoding("shift-jis"))) { foreach (string line in lines) { sw.WriteLine(line); } } |
ちなみに、下記はメソッド化したサンプルです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/// <summary> /// リスト型の文字列データをファイルに保存する /// </summary> /// <param name="filename">ファイル名</param> /// <param name="lines">保存したいデータ配列(又はリスト)</param> /// <param name="append">追加書き込みモード false:上書き true:追加 省略時はfalse</param> /// <param name="encode">エンコード文字列:省略時はshift-jis</param> private void WriteCsv(string filename,IEnumerable<string> lines, bool append = false, string encode="shift-jis") { using (StreamWriter sw = new StreamWriter(filename, append, Encoding.GetEncoding(encode))) { foreach (string line in lines) { sw.WriteLine(line); } } } |
表形式(2次元配列)のデータをCSVとして書き込む
C#では表形式(2次元配列)のデータを色々な形で持つことが可能ですが、ここでは代表的なDataTableと、リストの入れ子で保持されたデータについて、CSV出力する方法を紹介します。
DataTableの中身をCSVとして出力する
File.WriteAllLines 又は StreamWriter & WriteLine で書き込めるように、1行ごとに各列の値をカンマ区切りで連結すれば良いのです。
ポイントは、列名とデータは別のクラスに格納されているので、個別に取得する必要があるという点です。
以下はテストデータを作成するプログラムです。
1 2 3 4 5 6 7 8 |
using System.Data //DataTableを使う場合は、冒頭にこの1行を追加 DataTable dt = new DataTable(); dt.Columns.Add("メーカー"); dt.Columns.Add("製品名"); dt.Rows.Add(new string[]{ "Dell", "Inspiron" }); dt.Rows.Add(new string[]{ "富士通", "FMV LIFEBOOK" }); dt.Rows.Add(new string[]{ "NEC", "LAVIE Direct" }); |
上記のプログラムを実行すると、DataTableの Columns には列名が、Rows にはデータが格納されています。
File.WriteAllLines や StreamWriter & WriteLine でCSVとして書き込む場合、各行はカンマ区切りにしておく必要があるので、次の様になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//CSV出力用変数の作成 List<string> lines = new List<string>(); //列名をカンマ区切りで1行に連結 List<string> header = new List<string>(); foreach (DataColumn dr in dt.Columns) { header.Add(dr.ColumnName); } lines.Add(string.Join(",", header)); //列の値をカンマ区切りで1行に連結 foreach (DataRow dr in dt.Rows) { lines.Add(string.Join(",", dr.ItemArray)); } |
ちなみに、Linqを使えば上記の処理は以下のようにも記述できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//CSV出力用変数の作成 List<string> lines = new List<string>(); //列名をカンマ区切りで1行に連結 lines.Add(string.Join(",",dt.Columns.Cast<DataColumn>() .Select(i =>i.ColumnName).ToArray())); //列の値をカンマ区切りで1行に連結 dt.AsEnumerable().Select(i=> { lines.Add(string.Join(",", i.ItemArray)); return 0; }).ToArray(); |
これらのプログラムを実行すると、行ごとに列がカンマで連結された結果が lines に格納されますので、あとは File.WriteAllLines や StreamWriter & WriteLine を使ってCSVに書き込むだけです。
リスト形式 の入れ子に格納された中身をCSVとして書き込む
こちらはDataTableよりも簡単です。
リストであろうと文字列配列であろうと、各行の中身をカンマで単純に連結するだけで事が足ります。
以下はリストの中に文字列配列を持たせたテストデータの作成プログラムです。
1 2 3 4 5 |
List<string[]> datas = new List<string[]>(); datas.Add(new string[]{"メーカー", "製品名"}); datas.Add(new string[]{"Dell", "Inspiron" }); datas.Add(new string[]{"富士通", "FMV LIFEBOOK" }); datas.Add(new string[]{"NEC", "LAVIE Direct" }); |
このテストデータに対して、1行づつ中身をカンマで連結すれば良いので、次の様になります。
1 2 3 4 5 6 |
List<string> lines = new List<string>(); foreach (var data in datas) { lines.Add(string.Join(",", data)); } |
こうして出来上がった lines を File.WriteAllLines や StreamWriter & WriteLine でCSVに書き込みます。
ちなみに、Linq で書くと次の様になります。
1 |
List<string> lines = datas.Select(i => string.Join(",", i)).ToList(); |
列の値に改行コード、カンマ、ダブルクォートが含まれる場合の対応
データの中に改行コード、カンマ、ダブルクォートが含まれている場合、そのままCSVに書き込むとExcelやその他のプログラムで読み込んだ時、正しく読み込まれません。
CSVの仕様上、改行コードやカンマが含まれているデータはダブルクォートで括る必要がありますし、ダブルクォートが含まれている場合はダブルクォートを2個連ねる必要があります。
これもいくつかの方法がありますが、例えば以下のように配列を受取って、無条件にダブルクォートの処理をしてしまう方法があります。
1 2 3 4 5 6 7 |
public IEnumerable<string> DoubleQuote(IEnumerable<string> items) { return items.Select(i => "\"" + i.Replace("\"", "\"\"") + "\""); } |
もう少し厳密にするのであれば、次の様にカンマや改行が含まれているかチェックし、含まれていればダブルクォートで括れば良いかと思います。
1 2 3 4 5 6 |
public IEnumerable<string> DoubleQuote(IEnumerable<string> items) { return items.Select(i => i.Replace("\"", "\"\"")) .Select(i => (i.Contains(",") || i.Contains("\n") ? "\"" + i + "\"" : i)); } |
この関数を使って、lines の作成時に適用するには次の様に記述します。
1 2 3 4 5 6 7 8 |
List<string[]> datas = new List<string[]>(); datas.Add(new string[]{"メーカー", "製品名"}); datas.Add(new string[]{"Dell", "Inspiron\"" }); datas.Add(new string[]{"富士通", "FMV LIFEBOOK\n" }); datas.Add(new string[]{"NEC", "LAVIE,Direct" }); List<string> lines = datas.Select(i => string.Join(",", DoubleQuote(i))).ToList(); File.WriteAllLines(@"d:\mydata.csv", lines, Encoding.GetEncoding("shift-jis")); |
DataTableの中身を直接CSVに書き込む関数(コピペ可能)
DataTableの中身を一旦リストに入れてから File.WriteAllLines や StreamWriter & WriteLine で書き出す方法では、一時的にメモリに2倍のデータを保持することになります。
そこで、 ループの中で DataTable から1行づつ取したらずぐ WriteLine でCSVに書き込むプログラムの例を掲載しておきます。
一般的には、文字列が含まれる列に対して、カンマや改行コードが含まれるか否かに関わらずダブルクォートで括ることが多く、またそのようなCSVはヘッダが全てダブルクォートで括られています。
そこで、このサンプルでは、qcols という引数に0から始まる列番号を配列で指定することで、その列番号に対してダブルクォートの処理を行うようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/// <summary> /// リスト型の文字列データをファイルに保存する /// </summary> /// <param name="filename">ファイル名</param> /// <param name="dt">保存したいDataTable</param> /// <param name="qcols">ダブルクォートで括りたい列の番号 int[]{1,5,7} </param> /// <param name="append">追加書き込みモード false:上書き true:追加 省略時はfalse</param> /// <param name="encode">エンコード文字列:省略時はshift-jis</param> private void WriteCsv(string filename, DataTable dt,int[] qcols = null,bool append = false, string encode = "shift-jis") { using (StreamWriter sw = new StreamWriter(filename, append, Encoding.GetEncoding(encode))) { string dquote = (qcols == null) ? "" : "\""; sw.WriteLine(string.Join(",",dt.Columns.Cast<DataColumn>() .Select(i => dquote + i.ColumnName + dquote).ToArray())); foreach (DataRow dr in dt.Rows) { sw.WriteLine(string.Join(",", DoubleQuote(dr.ItemArray, qcols))); } } string[] DoubleQuote(IEnumerable<object> p_item,int[] p_qcols) { int cnt = 0; return p_item.Select(i=>(p_qcols != null && p_qcols.Contains(cnt++)) ? "\"" + i.ToString() .Replace("\"", "\"\"") + "\"" : i.ToString()).ToArray(); } } |
まとめ
今回はC#を使ったCSV書き込みにおいて、File.WriteAllLines と StreamWriter を使う方法について解説しました。
単にカンマ区切りで出力することは非常に簡単ですが、値にカンマやダブルクォートが含まれる場合、すこしだけ考慮する必要があるので、その点についても解説致しました。
CSVについては下記にも記事を書いていますので、興味のある方は是非ご一読ください。
今回の記事が、皆さんのプログラミングの一助になれば幸いです。
コメント