如何提高自定义 BindingList 上 AddRange 方法的性能?
How can I improve performance of an AddRange method on a custom BindingList?
我有一个自定义 BindingList,我想为其创建一个自定义 AddRange 方法。
public class MyBindingList<I> : BindingList<I>
{
...
public void AddRange(IEnumerable<I> vals)
{
foreach (I v in vals)
Add(v);
}
}
我的问题是大型集合的性能很糟糕。我现在正在调试的案例是尝试添加大约 30,000 条记录,并且花费了无法接受的时间。
在线查看此问题后,问题似乎在于 Add
的使用每次添加都会调整数组的大小。 This answer 我想总结为:
If you are using Add, it is resizing the inner array gradually as needed (doubling)
我可以在我的自定义 AddRange
实现中做什么来指定 BindingList 需要根据项目计数调整大小,而不是让它在添加每个项目时不断地重新分配数组?
您可以在构造函数中传入一个列表并使用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();
}
}
}
您不能使用 _baseList.AddRange
,因为 BindingList<T>
不会挂接 PropertyChanged 事件。您可以通过在 AddRange 之后为每个项目调用私有方法 HookPropertyChanged 来仅使用反射来绕过它。然而,这只有在 vals
(您的方法参数)是一个集合时才有意义。否则你冒着枚举两次的风险。
这是您在不编写自己的 BindingList 的情况下最接近 "optimal" 的方法。
这应该不会太难,因为您可以从 BindingList 复制源代码并根据需要更改部分。
CSharpie 在他的 中解释说,性能不佳是由于 ListChanged
事件在每次 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();
}
}
}
这样,根据您的需要,您甚至可能不需要编写自己的 BindingList
-子类。
我有一个自定义 BindingList,我想为其创建一个自定义 AddRange 方法。
public class MyBindingList<I> : BindingList<I>
{
...
public void AddRange(IEnumerable<I> vals)
{
foreach (I v in vals)
Add(v);
}
}
我的问题是大型集合的性能很糟糕。我现在正在调试的案例是尝试添加大约 30,000 条记录,并且花费了无法接受的时间。
在线查看此问题后,问题似乎在于 Add
的使用每次添加都会调整数组的大小。 This answer 我想总结为:
If you are using Add, it is resizing the inner array gradually as needed (doubling)
我可以在我的自定义 AddRange
实现中做什么来指定 BindingList 需要根据项目计数调整大小,而不是让它在添加每个项目时不断地重新分配数组?
您可以在构造函数中传入一个列表并使用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();
}
}
}
您不能使用 _baseList.AddRange
,因为 BindingList<T>
不会挂接 PropertyChanged 事件。您可以通过在 AddRange 之后为每个项目调用私有方法 HookPropertyChanged 来仅使用反射来绕过它。然而,这只有在 vals
(您的方法参数)是一个集合时才有意义。否则你冒着枚举两次的风险。
这是您在不编写自己的 BindingList 的情况下最接近 "optimal" 的方法。 这应该不会太难,因为您可以从 BindingList 复制源代码并根据需要更改部分。
CSharpie 在他的 ListChanged
事件在每次 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();
}
}
}
这样,根据您的需要,您甚至可能不需要编写自己的 BindingList
-子类。