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