忘了為什麼要把IEnumerable<T>轉成DataTable,不過這個需求應該挺常見,恰好今天看到以前的一段代碼,有些想法就記錄下來。
IEnumerable<T>中的T是泛型,咱們就不能事先知道T都有哪些屬性,是以建立出來的DataTable也就不能預先設定列。遇到這種情況,首先就想到反射。
1 public static DataTable ToDataTable<T>(IEnumerable<T> collection)
2 {
3 var props = typeof(T).GetProperties();
4 var dt = new DataTable();
5 dt.Columns.AddRange(props.Select(p => new DataColumn(p.Name, p.PropertyType)).ToArray());
6 if (collection.Count() > 0)
7 {
8 for (int i = 0; i < collection.Count(); i++)
9 {
10 ArrayList tempList = new ArrayList();
11 foreach (PropertyInfo pi in props)
12 {
13 object obj = pi.GetValue(collection.ElementAt(i), null);
14 tempList.Add(obj);
15 }
16 object[] array = tempList.ToArray();
17 dt.LoadDataRow(array, true);
18 }
19 }
20 return dt;
21 }
反射效率低,自然而然我們又想到了表達式樹這個東西,表達式樹的其中一個作用就是實作了反射的功能同時避免了反射帶來的效率問題。
首先咱打算構造一個形如obj=>obj.Property的表達式樹。
1 //構造委托類似Func<User, int> getAge = u => u.Age;
2 static Func<T, object> GetGetDelegate<T>(PropertyInfo p)
3 {
4 var param_obj = Expression.Parameter(typeof(T), "obj");
5 //lambda的方法體 u.Age
6 var pGetter = Expression.Property(param_obj, p);
7 //編譯lambda
8 return Expression.Lambda<Func<T, object>>(pGetter, param_obj).Compile();
9 }
10
11 static object FastGetValue<T>(this PropertyInfo property, T t)
12 {
13 return GetGetDelegate<T>(property)(t);
14 }
然後我們就可以将上述反射代碼改成如下:
1 public static DataTable ToTable<T>(this IEnumerable<T> collection)
2 {
3 var props = typeof(T).GetProperties();
4
5 var dt = new DataTable();
6 dt.Columns.AddRange(
7 props.Select(p => new DataColumn(p.Name, p.PropertyType)).ToArray()
8 );
9
10 collection.ToList().ForEach(
11 i => dt.Rows.Add(props.Select(p => p.FastGetValue(i)).ToArray())
12 );
13
14 return dt;
15 }
很好,咱們沒用到反射就把工作完成了。但是效率真的有提升嗎?我看未必。後者隻是将前者的pi.GetValue(collection.ElementAt(i), null)改成p => p.FastGetValue(i),而FastGetValue會每次都去構造表達式樹然後編譯Lambda,針對IEnumerable<T>中的每一條資料都重複構造相同的屬性委托。兩者效率我沒測過,不過這個方式肯定不完美。改進的方式有很多,比如将屬性和GetGetDelegate構造的委托作為鍵值對緩存起來供後續循環使用等等。下面是我想到的較好的一種解決方法:
1 static Func<T, object[]> GetGetDelegate<T>(PropertyInfo[] ps)
2 {
3 var param_obj = Expression.Parameter(typeof(T), "obj");
4 Expression newArrayExpression = Expression.NewArrayInit(typeof(object), ps.Select(p => Expression.Property(param_obj, p)));
5 return Expression.Lambda<Func<T, object[]>>(newArrayExpression, param_obj).Compile();
6 }
這裡我将屬性委托從傳回單個屬性值變為傳回所有屬性值數組,我們就可以這麼使用。
1 public static DataTable ToTable<T>(this IEnumerable<T> collection)
2 {
3 var props = typeof(T).GetProperties();
4 var func = GetGetDelegate<T>(props);
5 var dt = new DataTable();
6 dt.Columns.AddRange(
7 props.Select(p => new DataColumn(p.Name, p.PropertyType)).ToArray()
8 );
9 collection.ToList().ForEach(i => dt.Rows.Add(func(i)));
10
11 return dt;
12 }
上述代碼可知,使用時隻需要構造一次委托即可。另外,這個方法我沒使用過,大家可以試試看,哇哈哈哈哈。
轉載請注明本文出處:
http://www.cnblogs.com/newton/archive/2013/01/09/2853083.html