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;
    }

然而,在大量数据上,性能不是很好。是否有我无法看到的明显的性能修复?

IEnumerable作为数据表的性能问题

首先,几个非完美的问题:

  1. 枚举对象中第一项的类型可以是T的一个子类,它定义了其他项中不存在的属性。为了避免这可能导致的问题,请使用T类型作为属性列表的源。
  2. 该类型的属性要么没有getter,要么有索引getter。你的代码不应该试图读取它们的值。

在性能方面,我可以看到在反射和数据表加载方面的潜在改进:

  1. 缓存属性getter并直接调用它们。
  2. 避免通过名称访问数据行列来设置行值。
  3. 在添加行时将数据表置于"数据加载"模式

使用这些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>

这样做的主要原因是:

  1. 你正在从一个IEnumerable到一个DataTable,这实际上是从一个操作到一个缓冲操作。

    • streaming:使用yield return,以便只在需要时从枚举中取出结果。它不像foreach

    • 那样一次迭代整个集合。
    • Buffered:将所有结果放入内存中(例如,填充的集合,数据表或数组),因此所有的开销都是一次发生的

  2. 如果你可以使用IEnumerable返回类型,那么你可以自己使用yield return关键字,这意味着你可以将所有反射的成本分摊出去,而不是一次全部发生。