專案從 DataSet/DataTable 導向轉型為 POCO/List<T>,頻繁需要雙向轉換。利用 C# 3.0 擴充方法,分別擴充 DataTable 與 IEnumerable<T>,實現 dt.ToList<T>() 與 list.ToDataTable()。
public static class DataTableExtensions {
// DataTable → List
public static List ToList(this DataTable dt) where T : class, new() {
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => dt.Columns.Contains(p.Name)).ToArray();
var list = new List(dt.Rows.Count);
foreach (DataRow row in dt.Rows) {
T obj = new T();
foreach (var p in props) {
var val = row[p.Name];
if (val != DBNull.Value) p.SetValue(obj, val);
}
list.Add(obj);
}
return list;
}
// IEnumerable → DataTable
public static DataTable ToDataTable(this IEnumerable source) where T : class {
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).ToArray();
var dt = new DataTable();
foreach (var p in props) dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
foreach (T item in source) {
var row = dt.NewRow();
foreach (var p in props) row[p.Name] = p.GetValue(item) ?? DBNull.Value;
dt.Rows.Add(row);
}
return dt;
}
}
public static class FastConverter {
// 快取編譯後的轉換委派
static readonly ConcurrentDictionary> _rowToObjCache = new();
static readonly ConcurrentDictionary> _objToRowCache = new();
public static List ToListFast(this DataTable dt) where T : class, new() {
var converter = (Func)_rowToObjCache.GetOrAdd(typeof(T), t => {
var rowParam = Expression.Parameter(typeof(DataRow), "row");
var newExpr = Expression.New(t);
var bindings = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => dt.Columns.Contains(p.Name))
.Select(p => Expression.Bind(p, Expression.Convert(
Expression.Property(rowParam, "Item", Expression.Constant(p.Name)), p.PropertyType)));
var memberInit = Expression.MemberInit(newExpr, bindings);
var lambda = Expression.Lambda>(memberInit, rowParam);
return lambda.Compile();
});
return dt.Rows.Cast().Select(converter).ToList();
}
public static DataTable ToDataTableFast(this IEnumerable source) where T : class {
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).ToArray();
var dt = new DataTable();
foreach (var p in props) dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
var converter = _objToRowCache.GetOrAdd(typeof(T), t => {
var objParam = Expression.Parameter(typeof(object), "obj");
var rowParam = Expression.Parameter(typeof(DataRow), "row");
var assigns = props.Select(p => Expression.Assign(
Expression.Property(rowParam, "Item", Expression.Constant(p.Name)),
Expression.Convert(Expression.Property(Expression.Convert(objParam, t), p.Name), typeof(object))));
var block = Expression.Block(assigns);
return Expression.Lambda>(block, objParam, rowParam).Compile();
});
foreach (var item in source) {
var row = dt.NewRow();
converter(item, row);
dt.Rows.Add(row);
}
return dt;
}
}
| 方案 | DataTable→List (ms) | List→DataTable (ms) |
|---|---|---|
| 反射版 | ~1200 | ~900 |
| 運算式樹版 | ~180 | ~150 |
運算式樹版首次呼叫支付編譯成本,後續呼叫極快,適合高頻轉換場景。
FastMember, AutoMapper)。DBNull.Value 處理、Nullable 型別欄位型別對應、大型物件圖遞迴轉換深度。