IEnumerable作为数据表的性能问题
本文关键字:性能 问题 数据表 IEnumerable | 更新日期: 2023-09-27 18:05:46
我有以下扩展,它从IEnumerable
生成DataTable
:
public static DataTable AsDataTable<T>(this IEnumerable<T> enumerable)
{
DataTable table = new DataTable();
T first = enumerable.FirstOrDefault();
if (first == null)
return table;
PropertyInfo[] properties = first.GetType().GetProperties();
foreach (PropertyInfo pi in properties)
table.Columns.Add(pi.Name, pi.PropertyType);
foreach (T t in enumerable)
{
DataRow row = table.NewRow();
foreach (PropertyInfo pi in properties)
row[pi.Name] = t.GetType().InvokeMember(pi.Name, BindingFlags.GetProperty, null, t, null);
table.Rows.Add(row);
}
return table;
}
然而,在大量数据上,性能不是很好。是否有我无法看到的明显的性能修复?
首先,几个非完美的问题:
- 枚举对象中第一项的类型可以是T的一个子类,它定义了其他项中不存在的属性。为了避免这可能导致的问题,请使用T类型作为属性列表的源。
- 该类型的属性要么没有getter,要么有索引getter。你的代码不应该试图读取它们的值。
在性能方面,我可以看到在反射和数据表加载方面的潜在改进:
- 缓存属性getter并直接调用它们。
- 避免通过名称访问数据行列来设置行值。
- 在添加行时将数据表置于"数据加载"模式
使用这些mod,您将最终得到如下内容:
public static DataTable AsDataTable<T>(this IEnumerable<T> enumerable)
{
if (enumerable == null)
{
throw new ArgumentNullException("enumerable");
}
DataTable table = new DataTable();
if (enumerable.Any())
{
IList<PropertyInfo> properties = typeof(T)
.GetProperties()
.Where(p => p.CanRead && (p.GetIndexParameters().Length == 0))
.ToList();
foreach (PropertyInfo property in properties)
{
table.Columns.Add(property.Name, property.PropertyType);
}
IList<MethodInfo> getters = properties.Select(p => p.GetGetMethod()).ToList();
table.BeginLoadData();
try
{
object[] values = new object[properties.Count];
foreach (T item in enumerable)
{
for (int i = 0; i < getters.Count; i++)
{
values[i] = getters[i].Invoke(item, BindingFlags.Default, null, null, CultureInfo.InvariantCulture);
}
table.Rows.Add(values);
}
}
finally
{
table.EndLoadData();
}
}
return table;
}
而不是:
row[pi.Name] = t.GetType().InvokeMember(pi.Name, BindingFlags.GetProperty, null, t, null);
使用:
row[pi.Name] = pi.GetValue(t, null);
你总是可以使用像fasterreflect这样的库来发出IL,而不是对列表中每个项目的每个属性使用true Reflection。不确定DataTable
有什么问题
或者,如果此代码不试图成为通用解决方案,您可以始终将IEnumerable
中的任何类型转换为DataRow
,从而避免全部反射。
您可能对此没有选择,但可以查看代码的体系结构,看看是否可以避免使用DataTable
而自己返回IEnumerable<T>
。
这样做的主要原因是:
-
你正在从一个IEnumerable到一个DataTable,这实际上是从一个流操作到一个缓冲操作。
-
streaming:使用
yield return
,以便只在需要时从枚举中取出结果。它不像foreach
那样一次迭代整个集合。 Buffered:将所有结果放入内存中(例如,填充的集合,数据表或数组),因此所有的开销都是一次发生的
-
-
如果你可以使用IEnumerable返回类型,那么你可以自己使用
yield return
关键字,这意味着你可以将所有反射的成本分摊出去,而不是一次全部发生。