最近はPythonの記事ばかり書いていたので、今回はC#を使ったCSV書き込みの方法について解説します。
とりあえず使いたい人はコピペで使ってください。
中身を理解してカスタマイズしたい方は、最後まで記事をお読みください。
CSVの書き込み方法
CSVは項目がカンマで区切られたテキストファイルです。
CSVの仕様を詳しくお知りになりたい方は、「2分で分かる!csvファイルの仕様と知っておきたいポイント」の記事を合わせてお読みください。
C#でテキストファイルを書き込み(保存)するには、方法として大きく2通りが用意されています。
1つは File.WriteAllLines() を使う方法、もう1つは StreamWriterを使う方法です。
いずれにせよ、これらを使うにはソースコードの冒頭に参照の記述 (using ~)が必要です。
次の1行を記述しておいてください。
using System.IO;
File.WriteAllLines を使う方法
File.WriteAllLines は、第2引数で渡された文字列の配列(又はリスト)を一括で書き込んでくれる便利なメソッドです。
既に同じファイル名が存在する場合、上書きするか追加書き込みするかによって、ことなるメソッドを呼び分ける必要があります。
上書き ⇒ File.WriteAllLines(ファイル名,データ,文字コード)
追加 ⇒ File.AppendAllLines(ファイル名,データ,文字コード)
文字コードの引数は省略可能ですが、省略時は UTF-8 で書き込まれます。
CSVファイルをEXCELで開く場合は文字化けしますので、EXCELで開くことが前提なら、Encoding.GetEncoding("shift-jis") を指定する必要があります。
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行づつ書き込むタイミングで何らかの処理を行いたい場合、もしくは順次送られてくるデータに対して、メモリ内に蓄えずに都度ファイル出力したい場合に使用します。
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);
}
}
ちなみに、下記はメソッド化したサンプルです。
/// <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行ごとに各列の値をカンマ区切りで連結すれば良いのです。
ポイントは、列名とデータは別のクラスに格納されているので、個別に取得する必要があるという点です。
以下はテストデータを作成するプログラムです。
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として書き込む場合、各行はカンマ区切りにしておく必要があるので、次の様になります。
//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を使えば上記の処理は以下のようにも記述できます。
//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よりも簡単です。
リストであろうと文字列配列であろうと、各行の中身をカンマで単純に連結するだけで事が足ります。
以下はリストの中に文字列配列を持たせたテストデータの作成プログラムです。
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行づつ中身をカンマで連結すれば良いので、次の様になります。
List<string> lines = new List<string>();
foreach (var data in datas)
{
lines.Add(string.Join(",", data));
}
こうして出来上がった lines を File.WriteAllLines や StreamWriter & WriteLine でCSVに書き込みます。
ちなみに、Linq で書くと次の様になります。
List<string> lines = datas.Select(i => string.Join(",", i)).ToList();
列の値に改行コード、カンマ、ダブルクォートが含まれる場合の対応
データの中に改行コード、カンマ、ダブルクォートが含まれている場合、そのままCSVに書き込むとExcelやその他のプログラムで読み込んだ時、正しく読み込まれません。
CSVの仕様上、改行コードやカンマが含まれているデータはダブルクォートで括る必要がありますし、ダブルクォートが含まれている場合はダブルクォートを2個連ねる必要があります。
これもいくつかの方法がありますが、例えば以下のように配列を受取って、無条件にダブルクォートの処理をしてしまう方法があります。
public IEnumerable<string> DoubleQuote(IEnumerable<string> items)
{
return items.Select(i => "\"" + i.Replace("\"", "\"\"") + "\"");
}
もう少し厳密にするのであれば、次の様にカンマや改行が含まれているかチェックし、含まれていればダブルクォートで括れば良いかと思います。
public IEnumerable<string> DoubleQuote(IEnumerable<string> items)
{
return items.Select(i => i.Replace("\"", "\"\""))
.Select(i => (i.Contains(",") || i.Contains("\n") ? "\"" + i + "\"" : i));
}
この関数を使って、lines の作成時に適用するには次の様に記述します。
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から始まる列番号を配列で指定することで、その列番号に対してダブルクォートの処理を行うようにしています。
/// <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については下記にも記事を書いていますので、興味のある方は是非ご一読ください。
今回の記事が、皆さんのプログラミングの一助になれば幸いです。
コメント