Attributeを使って、IEnumerable<T>をTSV形式に変換する

#はじめに

今時、JSONでデータ書き出せばいいじゃんとか思うけど、エクセル等で使っている人いるし、
TSVで、オブジェクトを書き出したいというのはある。

とはいえ、オブジェクトの英語の変数名より、日本語の変数名で書き出したいという要望もあるし、
楽に、.ToTsv()ぐらいで、変換して欲しいというのもある。

そういうのを作った。（今更感あるけど・・・）

# Attributeを作る

```csharp:
  public class TsvColumnNameAttribute : Attribute
    {
        public string ColumnName { get; set; }

        public TsvColumnNameAttribute()
        {

        }

        public TsvColumnNameAttribute(string name)
        {
            ColumnName = name;
        }

        public string GetColumnName(PropertyInfo propertyInfo)
        {
            if(string.IsNullOrEmpty( ColumnName))
            {
                return propertyInfo.Name;
            }
            else
            {
                return ColumnName;
            }
        }
    }
```

[TsvColumnNameAttribute]という感じで、変数の前につけるやつを自作。ただ単に、変数に名前をつけるだけ。名前がないとき、自身の名前を使う。

# (type).GetProperty().GetValue の式木版

(type).GetProperty().GetValue は遅いと評判なので、式木版を作る便利メソッド作成。
関数として、オブジェクトを入れると、対象の変数を返すものとなっている。割と便利。

```csharp:
    public static class Reflection
    {
        /// <summary>
        /// (type).GetProperty().GetValue の式木版
        /// </summary>
        /// <typeparam name="T">対象の型</typeparam>
        /// <typeparam name="T1">返す型</typeparam>
        /// <param name="property_name"></param>
        /// <returns></returns>
        public static Func<T, T1> GetValueFunc<T, T1>(string property_name)
        {
            var p = Expression.Parameter(typeof(T));
            var lambda = Expression.Lambda(Expression.Property(p, property_name), p);
            return (Func<T, T1>)lambda.Compile();
        }
    }
```

# ToTsvメソッド

[TsvColumnNameAttribute]属性を調べつつ、ヘッダーの書き出し、関数の作成し、
Listにたいして、TSVで書き出すということをする。
IEnumerable版は、WPF用。違いは、はじめの型の取得だけ。

```csharp:
   public static class Tsv
    {
        public static string ToTsv<T>(this IEnumerable<T> list)
        {
            var properties = typeof(T).GetProperties();
            var columns = properties.Select(n => new { PropertyInfo = n, TsvNameAttribute = n.GetCustomAttribute<TsvColumnNameAttribute>() }).Where(n=>n.TsvNameAttribute != null);

            var columns_text = string.Join("\t", columns.Select(n => n.TsvNameAttribute.GetColumnName(n.PropertyInfo)));
            var method = typeof(Reflection).GetMethod("GetValueFunc");

            List<Delegate> delegeteList = new List<Delegate>();
            foreach (var item in columns)
            {
                var constructed = method.MakeGenericMethod(new Type[] { typeof(T), item.PropertyInfo.PropertyType });
                delegeteList.Add((Delegate)constructed.Invoke(null, new object[] { item.PropertyInfo.Name }));
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendLine(columns_text);
            foreach (var line in list)
            {
                foreach (var item in delegeteList)
                {
                    sb.Append(item.DynamicInvoke(line).ToString() + "\t");
                }
                sb.AppendLine();
            }
            return sb.ToString();
        }

        public static string ToTsv(this IEnumerable list)
        {
            // 入れの子のGenericには対応していない
            var type = list.GetType().GetGenericArguments().First();
            var properties = type.GetProperties();
            var columns = properties.Select(n => new { PropertyInfo = n, TsvNameAttribute = n.GetCustomAttribute<TsvColumnNameAttribute>() }).Where(n => n.TsvNameAttribute != null);

            var columns_text = string.Join("\t", columns.Select(n => n.TsvNameAttribute.GetColumnName(n.PropertyInfo)));
            var method = typeof(Reflection).GetMethod("GetValueFunc");

            List<Delegate> delegeteList = new List<Delegate>();
            foreach (var item in columns)
            {
                var constructed = method.MakeGenericMethod(new Type[] { type, item.PropertyInfo.PropertyType });
                delegeteList.Add((Delegate)constructed.Invoke(null, new object[] { item.PropertyInfo.Name }));
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendLine(columns_text);
            foreach (var line in list)
            {
                foreach (var item in delegeteList)
                {
                    sb.Append(item.DynamicInvoke(line).ToString() + "\t");
                }
                sb.AppendLine();
            }
            return sb.ToString();
        }

   }
```

# 使い方

出力するプロパティに、[TsvColumnName]をつける。つけないものは出力されない。

```csharp:
    public class Row
    {
        [TsvColumnName]
        public int Id { get; set; }
        [TsvColumnName("タイトル")]
        public string Title { get; set; }
        public string Url { get; set; }
    }
```

出力するときは、ToTsvで出力。

```csharp:
List<Row> list = new List<Row>();
list.Add()....

list.ToTsv();
```

## WPFの場合

ViewModelにRowsToTsvCommandを追加（MvvmLightを使っています）

```csharp:
  　　  private void RowsToTsv(IEnumerable list)
        {
            if (list != null)
            {                
　　　　　　　　//クリップボードに出力
               Clipboard.SetText(list.ToTsv());
               MessageBox.Show("クリップボードに格納しました");                
            }
        }
        #region AdvertisementCountRowsToTsv Command
        /// <summary>
        /// Gets the AdvertisementCountRowsToTsv.
        /// </summary>
        public RelayCommand<IEnumerable> RowsToTsvCommand
        {
            get { return _RowsToTsvCommand ?? (_RowsToTsvCommand = new RelayCommand<IEnumerable>((n) => { RowsToTsv(n); })); }
        }
        private RelayCommand<IEnumerable> _RowsToTsvCommand;
        #endregion
```

XAML側

```xml:
  <Button Content="Clip" Command="{Binding RowsToTsvCommand}" CommandParameter="{Binding Rows}" />
```
Bindingで指定したListに対して、Tsv出力ができるようになる。

# まとめ
とても捗った。似たようなの探すと見つかりそうだけど。