如何无需for循环且以最快方式将属性匹配的DataTable转为List<T>
不使用for循环将DataTable转换为List的方法及最优实现
核心逻辑
避免显式for循环的本质是借助框架内置迭代器、反射或表达式树来隐式处理DataTable的每一行,同时通过预编译、缓存等手段追求最高转换性能。
方法1:LINQ + 反射(简洁易实现)
利用LINQ的AsEnumerable()遍历行集合,结合反射自动匹配属性与列名完成映射,无需手写显式for循环。适合小数据量场景。
示例代码:
public static List<T> DataTableToList<T>(DataTable dt) where T : new() { return dt.AsEnumerable() .Select(row => { T model = new T(); foreach (var prop in typeof(T).GetProperties()) { if (dt.Columns.Contains(prop.Name)) { var cellValue = row[prop.Name]; prop.SetValue(model, cellValue == DBNull.Value ? null : cellValue); } } return model; }) .ToList(); }
方法2:表达式树缓存映射委托(高性能通用方案)
反射的性能瓶颈在于重复调用SetValue,通过表达式树提前构建属性赋值的委托并缓存,后续转换直接调用委托,避免重复反射开销,这是通用场景下最快的实现方式。
示例代码:
public static class DataTableConverter { private static readonly Dictionary<Type, Delegate> _converterCache = new Dictionary<Type, Delegate>(); public static List<T> ToList<T>(DataTable dt) where T : new() { var converter = GetRowConverter<T>(dt); return dt.AsEnumerable().Select(row => converter(row)).ToList(); } private static Func<DataRow, T> GetRowConverter<T>(DataTable dt) where T : new() { Type modelType = typeof(T); if (_converterCache.TryGetValue(modelType, out var cachedDelegate)) { return (Func<DataRow, T>)cachedDelegate; } // 构建参数表达式:DataRow row var rowParam = Expression.Parameter(typeof(DataRow), "row"); // 构建实例创建表达式:new T() var newInstanceExpr = Expression.New(modelType); var propertyBindings = new List<MemberBinding>(); foreach (var prop in modelType.GetProperties()) { if (!dt.Columns.Contains(prop.Name)) continue; // 构建row[列名]的表达式 var cellValueExpr = Expression.Property(rowParam, "Item", Expression.Constant(prop.Name)); // 处理DBNull到null的转换 var safeValueExpr = Expression.Condition( Expression.Equal(cellValueExpr, Expression.Constant(DBNull.Value)), Expression.Constant(null, prop.PropertyType), Expression.Convert(cellValueExpr, prop.PropertyType) ); // 绑定属性赋值 propertyBindings.Add(Expression.Bind(prop, safeValueExpr)); } // 构建实例初始化表达式 var initExpr = Expression.MemberInit(newInstanceExpr, propertyBindings); // 编译为委托并缓存 var converter = Expression.Lambda<Func<DataRow, T>>(initExpr, rowParam).Compile(); _converterCache[modelType] = converter; return converter; } }
首次构建表达式树会有轻微开销,后续转换性能接近手写硬编码赋值,适合大数据量的通用类型转换。
方法3:手动属性映射(固定类型最优性能)
如果转换的类型固定,直接通过Field<T>方法手动映射属性,性能达到原生水平,但缺乏通用性。
示例代码:
public static List<User> DataTableToUsers(DataTable dt) { return dt.AsEnumerable() .Select(row => new User { Id = row.Field<int>("Id"), Username = row.Field<string>("Username"), CreateTime = row.Field<DateTime>("CreateTime") }) .ToList(); }
总结
- 轻量通用场景:选LINQ+反射,代码简洁无需手动配置。
- 高性能通用场景:选表达式树缓存方案,兼顾通用性与速度,是最优通用实现。
- 固定类型场景:选手动映射,性能最优但仅适用于特定类型。
内容的提问来源于stack exchange,提问作者jj243
相关产品推荐
相关产品推荐

