CSV出力
Attributeで遊んでみようと、とりあえずCSVを出力するのをやってみようかと。
ってことで、作ってみた。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; namespace CSVWriter { public class CsvWriter { #region 内部利用クラス /// <summary> /// CSVヘッダー情報 /// </summary> protected class CsvHeaderInfo { #region Property /// <summary> /// 表示ラベル /// </summary> public string Label { get; set; } /// <summary> /// プロパティ名 /// </summary> public string PropertyName { get; set; } #endregion /// <summary> /// コンストラクタ /// </summary> public CsvHeaderInfo() { } /// <summary> /// コンストラクタ /// </summary> /// <param name="label">表示ラベル</param> public CsvHeaderInfo(string label) { Label = label; PropertyName = label; } } /// <summary> /// CSV形式の文字を生成する /// </summary> protected class CsvDataFormatter : List<string> { /// <summary> /// データを設定する /// </summary> /// <param name="data">データ</param> /// <returns>データを追加した自身</returns> public CsvDataFormatter SetData(IEnumerable<string> data) { this.Clear(); this.AddRange(data); return this; } /// <summary> /// CSVの文字をエスケープする /// </summary> /// <param name="data">エスケープする文字列</param> /// <returns>エスケープした文字列</returns> private string Escape(string data) { if (string.IsNullOrEmpty(data)) { return ""; } int index = data.IndexOfAny(new char[] { '\n', ',', '"' }, 0); if (index != -1) { data = string.Format("\"{0}\"", data.Replace("\"", "\"\"")); } return data; } /// <summary> /// データをカンマ区切り文字列へ変換する /// </summary> /// <returns>カンマ区切り文字列</returns> public override string ToString() { return string.Join(",", this.Select(item => Escape(item))); } } #endregion protected List<CsvHeaderInfo> GetHeaderInfo(Type t) { var tergetHeaders = t.GetProperties().Where(prop => { return prop.GetCustomAttributes(typeof(CsvIgnoreAttribute), false).Count() == 0; }).Select(prop => { CsvHeaderInfo data = new CsvHeaderInfo(prop.Name); CsvHeaderAttribute attr = Attribute.GetCustomAttribute(prop, typeof(CsvHeaderAttribute)) as CsvHeaderAttribute; if (attr != null) { data.Label = attr.Label; } return data; }); List<CsvHeaderInfo> ret = new List<CsvHeaderInfo>(); ret.AddRange(tergetHeaders); return ret; } /// <summary> /// CSVファイルに出力する /// </summary> /// <param name="fileName">出力するファイル名</param> /// <param name="data">出力するデータ</param> /// <param name="outLabel">ラベルを出力するかどうかのフラグ</param> public void Write(string fileName, object[] data, bool outLabel = true) { StreamWriter sw = null; try { sw = new StreamWriter(fileName, false, Encoding.GetEncoding("shift_jis")); Write(sw, data, outLabel); } finally { if (sw != null) { sw.Close(); } } } /// <summary> /// ストリームにCSVデータを出力する /// </summary> /// <param name="fileName">出力するストリーム</param> /// <param name="data">出力するデータ</param> /// <param name="outLabel">ラベルを出力するかどうかのフラグ</param> public void Write(StreamWriter sw, object[] data, bool outLabel = true) { Type t = data[0].GetType(); List<CsvHeaderInfo> header = GetHeaderInfo(t); CsvDataFormatter formatter = new CsvDataFormatter(); if (outLabel) { // Output Label. string headerData = formatter.SetData(header.Select(p => p.Label)).ToString(); if (!string.IsNullOrEmpty(headerData)) { sw.WriteLine(headerData); } } // Output Data foreach (object one in data) { string oneLine = formatter.SetData(header.Select(p => { PropertyInfo prop = t.GetProperty(p.PropertyName); if (prop != null) { return prop.GetValue(one, null).ToString(); } return ""; })).ToString(); if (!string.IsNullOrEmpty(oneLine)) { sw.WriteLine(oneLine); } } } } #region Attribute /// <summary> /// CSVとして出力する際の属性 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public class CsvHeaderAttribute : Attribute { #region Property /// <summary> /// ラベル情報 /// </summary> public string Label { get; set; } #endregion /// <summary> /// コンストラクタ /// </summary> /// <param name="label">ラベル情報</param> public CsvHeaderAttribute(string label) { Label = label; } } /// <summary> /// CSVとして出力する際の無視属性 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public class CsvIgnoreAttribute : Attribute { } #endregion }
出力するデータのクラスを作成しておいて、作成したクラスの配列を渡せば、プロパティの内容をそのまま出力する様になっています。
行頭にラベルをありつけることもでき、CsvHeaderAttribute属性をプロパティに設定すれば、その値がラベルとして出力されます。
また、CsvIgnoreAttribute属性をプロパティに設定すれば出力されないように制御できます。
使用例はこんな感じ。
class FruitData { [CsvHeader("果物の名前")] public string Name { get; set; } [CsvHeader("値段")] public int Price { get; set; } [CsvIgnore()] public string Shop { get; set; } } class Program { static void Main(string[] args) { FruitData[] data = new[] { new FruitData() { Name = "リンゴ", Price = 120, Shop = "A商店" }, new FruitData() { Name = "オレンジ", Price = 100, Shop = "B商店" }, new FruitData() { Name = "リンゴ", Price = 150, Shop = "B商店" } }; CsvWriter writer = new CsvWriter(); writer.Write("Shopping.csv", data); } }
出力されたCSVはこんな感じ。
果物の名前,値段 リンゴ,120 オレンジ,100 リンゴ,150