はじめに
今時、JSONでデータ書き出せばいいじゃんとか思うけど、エクセル等で使っている人いるし、
TSVで、オブジェクトを書き出したいというのはある。
とはいえ、オブジェクトの英語の変数名より、日本語の変数名で書き出したいという要望もあるし、
楽に、.ToTsv()ぐらいで、変換して欲しいというのもある。
そういうのを作った。(今更感あるけど・・・)
Attributeを作る
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 は遅いと評判なので、式木版を作る便利メソッド作成。
関数として、オブジェクトを入れると、対象の変数を返すものとなっている。割と便利。
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用。違いは、はじめの型の取得だけ。
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]をつける。つけないものは出力されない。
public class Row
{
[TsvColumnName]
public int Id { get; set; }
[TsvColumnName("タイトル")]
public string Title { get; set; }
public string Url { get; set; }
}
出力するときは、ToTsvで出力。
List<Row> list = new List<Row>();
list.Add()....
list.ToTsv();
WPFの場合
ViewModelにRowsToTsvCommandを追加(MvvmLightを使っています)
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側
<Button Content="Clip" Command="{Binding RowsToTsvCommand}" CommandParameter="{Binding Rows}" />
Bindingで指定したListに対して、Tsv出力ができるようになる。
まとめ
とても捗った。似たようなの探すと見つかりそうだけど。