【问题标题】:IEnumerable as DataTable performance issueIEnumerable 作为 DataTable 性能问题
【发布时间】:2011-08-17 13:08:22
【问题描述】:

我有以下扩展,它从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;
    }

但是,在大量数据上,性能不是很好。是否有任何我看不到的明显性能修复?

【问题讨论】:

    标签: c# linq performance datatable ienumerable


    【解决方案1】:

    首先,几个非性能问题:

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

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

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

    使用这些模组,您最终会得到如下内容:

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

    【讨论】:

      【解决方案2】:

      而不是做:

      row[pi.Name] = t.GetType().InvokeMember(pi.Name, BindingFlags.GetProperty, null, t, null);
      

      使用:

      row[pi.Name] = pi.GetValue(t, null);
      

      【讨论】:

        【解决方案3】:

        您总是可以使用像 Fasterflect 这样的库来发出 IL,而不是对列表中每个项目的每个属性都使用真正的反射。不确定DataTable 的任何问题。

        或者,如果此代码不尝试成为通用解决方案,您可以始终将 IEnumerable 中的任何类型转换为 DataRow,从而避免一起反射。

        【讨论】:

        • 完全同意,反射是一项昂贵的操作。
        【解决方案4】:

        您可能对此别无选择,但可能会查看代码的架构,看看您是否可以避免使用 DataTable 而是自己返回 IEnumerable&lt;T&gt;

        这样做的主要原因是:

        1. 您正在从 IEnumerable 转到 DataTable,这实际上是从 流式 操作转到 缓冲 操作。

          • 流式传输:使用yield return,以便仅在需要时从枚举中提取结果。它不会像 foreach

          • 那样一次迭代整个集合
          • 缓冲:将所有结果拉入内存(例如填充的集合、数据表或数组),因此所有费用都一次性产生。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-24
          • 1970-01-01
          相关资源
          最近更新 更多