Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I improve performance of an AddRange method on a custom BindingList?

I have a custom BindingList that I want create a custom AddRange method for.

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

    public void AddRange(IEnumerable<I> vals)
    {
        foreach (I v in vals)
            Add(v);
    }
}

My problem with this is performance is terrible with large collections. The case I am debugging now is trying to add roughly 30,000 records, and taking an unacceptable amount of time.

After looking into this issue online, it seems like the problem is that the use of Add is resizing the array with each addition. This answer I think summarizes it as :

If you are using Add, it is resizing the inner array gradually as needed (doubling)

What can I do in my custom AddRange implementation to specify the size the BindingList needs to resize to be based on the item count, rather than letting it constantly re-allocate the array with each item added?

like image 447
Rachel Avatar asked Apr 10 '17 19:04

Rachel


People also ask

Is AddRange faster than foreach?

That means addrange is always faster than foreach . Does anyone know why? If you're worried about GC-sensitive perf (i.e. in a Unity game targeting mobile), AddRange will ToArray() the passed-in collection, which is an allocation. Bumping capacity and doing manual adds will probably be faster.

What does AddRange do in c#?

AddRange(IEnumerable<T>) Method is used to add the elements of the specified collection to the end of the List<T>. Properties of List: It is different from the arrays.

Is the AddRange feature used whenever adding multiple values to collection objects?

AddRange is used to add multiple elements. The add method inserts the item at the end of a collection. AddRange method is used to insert a set of records into a collection. The addRange method is also used to add an array of nodes that are previously created in the collection.


1 Answers

CSharpie explained in his answer that the bad performance is due to the ListChanged-event firing after each Add, and showed a way to implement AddRange for your custom BindingList.

An alternative would be to implement the AddRange functionality as an extension method for BindingList<T>. Based on on CSharpies implementation:

/// <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();
    }
  }
}

This way, depending on your needs, you might not even need to write your own BindingList-subclass.

like image 177
Timitry Avatar answered Sep 22 '22 01:09

Timitry