忘了為什么要把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
