7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

今時、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出力ができるようになる。

まとめ

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

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?