如何在自定义BindingList上提高AddRange方法的性能?

Rac*_*hel 7 c# bindinglist addrange

我有一个自定义BindingList,我想为其创建一个自定义的AddRange方法.

public class MyBindingList<I> : BindingList<I>
{
    ...

    public void AddRange(IEnumerable<I> vals)
    {
        foreach (I v in vals)
            Add(v);
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是大型集合的性能很糟糕.我正在调试的情况是尝试添加大约30,000条记录,并花费了不可接受的时间.

在线查看此问题之后,似乎问题在于使用Add每次添加时调整数组大小.我认为这个答案总结为:

如果您使用Add,它会根据需要逐渐调整内部数组的大小(加倍)

我可以在自定义AddRange实现中做什么来指定BindingList需要根据项目数量调整大小,而不是让它在每个项目添加时不断重新分配数组?

Tim*_*try 7

CSharpie 在他的回答中解释说,糟糕的性能是由于ListChanged在每个之后触发了-event Add,并展示了一种AddRange为您的自定义BindingList.

另一种方法是将该AddRange功能实现为BindingList<T>. 基于 CSharpies 实现:

/// <summary>
/// Extension methods for <see cref="System.ComponentModel.BindingList{T}"/>.
/// </summary>
public static class BindingListExtensions
{
  /// <summary>
  /// Adds the elements of the specified collection to the end of the <see cref="System.ComponentModel.BindingList{T}"/>,
  /// while only firing the <see cref="System.ComponentModel.BindingList{T}.ListChanged"/>-event once.
  /// </summary>
  /// <typeparam name="T">
  /// The type T of the values of the <see cref="System.ComponentModel.BindingList{T}"/>.
  /// </typeparam>
  /// <param name="bindingList">
  /// The <see cref="System.ComponentModel.BindingList{T}"/> to which the values shall be added.
  /// </param>
  /// <param name="collection">
  /// The collection whose elements should be added to the end of the <see cref="System.ComponentModel.BindingList{T}"/>.
  /// The collection itself cannot be null, but it can contain elements that are null,
  /// if type T is a reference type.
  /// </param>
  /// <exception cref="ArgumentNullException">values is null.</exception>
  public static void AddRange<T>(this System.ComponentModel.BindingList<T> bindingList, IEnumerable<T> collection)
  {
    // The given collection may not be null.
    if (collection == null)
      throw new ArgumentNullException(nameof(collection));

    // Remember the current setting for RaiseListChangedEvents
    // (if it was already deactivated, we shouldn't activate it after adding!).
    var oldRaiseEventsValue = bindingList.RaiseListChangedEvents;

    // Try adding all of the elements to the binding list.
    try
    {
      bindingList.RaiseListChangedEvents = false;

      foreach (var value in collection)
        bindingList.Add(value);
    }

    // Restore the old setting for RaiseListChangedEvents (even if there was an exception),
    // and fire the ListChanged-event once (if RaiseListChangedEvents is activated).
    finally
    {
      bindingList.RaiseListChangedEvents = oldRaiseEventsValue;

      if (bindingList.RaiseListChangedEvents)
        bindingList.ResetBindings();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这样,根据您的需要,您甚至可能不需要编写自己的BindingList-subclass。


CSh*_*pie 6

您可以在构造函数中传递List并使用List<T>.Capacity

但是我敢打赌,增加范围时,最显着的加速将来自暂停事件。因此,我在示例代码中都包含了这两种内容。

可能需要一些微调来处理一些最坏的情况,而其他情况则不能。

public class MyBindingList<I> : BindingList<I>
{
    private readonly List<I> _baseList;

    public MyBindingList() : this(new List<I>())
    {

    }

    public MyBindingList(List<I> baseList) : base(baseList)
    {
        if(baseList == null)
            throw new ArgumentNullException();            
        _baseList = baseList;
    }

    public void AddRange(IEnumerable<I> vals)
    {
        ICollection<I> collection = vals as ICollection<I>;
        if (collection != null)
        {
            int requiredCapacity = Count + collection.Count;
            if (requiredCapacity > _baseList.Capacity)
                _baseList.Capacity = requiredCapacity;
        }

        bool restore = RaiseListChangedEvents;
        try
        {
            RaiseListChangedEvents = false;
            foreach (I v in vals)
                Add(v); // We cant call _baseList.Add, otherwise Events wont get hooked.
        }
        finally
        {
            RaiseListChangedEvents = restore;
            if (RaiseListChangedEvents)
                ResetBindings();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您不能使用,_baseList.AddRange因为之后将BindingList<T>不会钩挂PropertyChanged事件。您可以通过在AddRange之后为每个项目调用私有方法HookPropertyChanged来仅使用反射来绕过此操作。但是,这仅在vals(您的方法参数)是一个集合的情况下才有意义。否则,您可能会有两次枚举可枚举的风险。

多数民众赞成在没有编写自己的BindingList的情况下可以最接近“最佳”。哪个不难,因为您可以从BindingList复制源代码并根据需要更改部分。