2016-02-24 49 views
0

我正在分析其他人编写的silverlight组件。 我发现很多热点和瓶颈,现在我碰到这一个:优化C#代码片段,ObservableCollection和AddRange

public static class CollectionExtensions 
{ 
    public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items) 
    { 
     foreach (var item in items) 
     { 
      collection.Add(item); 
     } 
    } 
} 

这种扩展方法,当然的AddRange方法添加到一个ObservableCollection,但它的计算相当密集。 有没有人有更好的执行,或者有关如何提高这块cose性能的任何建议?

谢谢

+3

因为你的代码工作,这个问题要在[代码审查]发布(http://codereview.stackexchange.com/) – ZwoRmi

回答

3

多次调用Add会导致INotifyCollectionChanged多次引发,导致UI重绘自身。

尽管Lee的回答在技术上是正确的,即在添加所有项目后提升Reset事件是正确的方法,但我从经验中发现,许多网格控件(例如)不主动支持Reset事件。

最普遍支持的选项是将集合修改为远离ObservableCollection并重新创建ObservableCollection属性本身。

与定义在VM如下您ObservableCollection换句话说...

private ObservableCollection<MyItem> _items; 
public ObservableCollection<MyItem> Items { 
    get { return _items;} 
    set 
    { 
     _items = value; 
     OnPropertyChanged(()=> Items); 
    } 
} 

...添加新的项目如下...

var tempColl = _items.ToList(); 
tempColl.AddRange(newItems); 
Items = new ObservableCollection(tempColl); 

另一件事来承担关于这种技术的想法是它是线程安全的,因为如果重新创建ObservableCollection,您可以从后台线程向ObservableCollection添加项目。正常ObservableCollection不能通过非Dispatcher线程的Add方法将项添加到它。

+0

很好的建议,但我担心这种“补偿行为”只会鼓励低质量的WPF控件可用。如果您使用的控件不能正确支持WPF绑定,请向控件的提供者提出错误,或者使用正常工作的东西。 –

+0

#rantOver。我还想指出,通常要避免使用集合的setter - https://msdn.microsoft.com/en-us/library/dn169389(v=vs.110).aspx。在ViewModel中可能会很好,但对于下一个开发者来说这并不好。 –

1

成本一般在这里是因为这是提高了每个单独添加更改通知。最好做的是创建一个新的集合实现,该集合实现为接受数据范围而优化。不必为每个更改引发更改通知,然后将绑定引擎处理为单个更新,您可以添加所有值,然后引发单个事件。此事件可以使大锤子Reset,或者您可以提供已更改的项目以及其更改的索引。

这是其AddRange方法是使用一个Reset通知的例子:

/// <summary> 
/// An implementation of <seealso cref="ObservableCollection{T}"/> that provides the ability to suppress 
/// change notifications. In sub-classes that allows performing batch work and raising notifications 
/// on completion of work. Standard usage takes advantage of this feature by providing AddRange method. 
/// </summary> 
/// <typeparam name="T">The type of elements in the list.</typeparam> 
public class ObservableList<T> : ObservableCollection<T> 
{ 
    #region Fields 
    private readonly Queue<PropertyChangedEventArgs> _notifications = new Queue<PropertyChangedEventArgs>(); 
    private readonly Queue<NotifyCollectionChangedEventArgs> _collectionNotifications = new Queue<NotifyCollectionChangedEventArgs>(); 
    private int _notificationSupressionDepth; 
    #endregion 

    public ObservableList() 
    { 
    } 
    public ObservableList(IEnumerable<T> collection) 
     : base(collection) 
    { 
    } 

    public void AddRange(IEnumerable<T> list) 
    { 
     using (SupressNotifications()) 
     { 
      foreach (var item in list) 
      { 
       Add(item); 
      } 
     } 
     OnPropertyChanged("Count"); 
     OnPropertyChanged("Item[]"); 
     OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 

    public void RemoveRange(IEnumerable<T> list) 
    { 
     using (SupressNotifications()) 
     { 
      foreach (var item in list) 
      { 
       Remove(item); 
      } 
     } 
     OnPropertyChanged("Count"); 
     OnPropertyChanged("Item[]"); 
     OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 

    public void ReplaceRange(IEnumerable<T> list) 
    { 
     using (SupressNotifications()) 
     { 
      Clear(); 
      foreach (var item in list) 
      { 
       Add(item); 
      } 
     } 
     OnPropertyChanged("Count"); 
     OnPropertyChanged("Item[]"); 
     OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 


    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     if (_notificationSupressionDepth == 0) 
     { 
      base.OnCollectionChanged(e); 
     } 
     else 
     { 
      //We cant filter duplicate Collection change events as this will break how UI controls work. -LC 
      _collectionNotifications.Enqueue(e); 
     } 
    } 

    protected override void OnPropertyChanged(PropertyChangedEventArgs e) 
    { 
     if (_notificationSupressionDepth == 0) 
     { 
      base.OnPropertyChanged(e); 
     } 
     else 
     { 
      if (!_notifications.Contains(e, NotifyEventComparer.Instance)) 
      { 
       _notifications.Enqueue(e); 
      } 
     } 
    } 

    protected void OnPropertyChanged(string propertyName) 
    { 
     OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); 
    } 

    protected IDisposable QueueNotifications() 
    { 
     _notificationSupressionDepth++; 
     return Disposable.Create(() => 
            { 
             _notificationSupressionDepth--; 
             TryNotify(); 
            }); 
    } 

    protected IDisposable SupressNotifications() 
    { 
     _notificationSupressionDepth++; 
     return Disposable.Create(() => 
     { 
      _notificationSupressionDepth--; 
     }); 
    } 

    private void TryNotify() 
    { 
     if (_notificationSupressionDepth == 0) 
     { 
      while (_collectionNotifications.Count > 0) 
      { 
       var collectionNotification = _collectionNotifications.Dequeue(); 
       base.OnCollectionChanged(collectionNotification); 
      } 

      while (_notifications.Count > 0) 
      { 
       var notification = _notifications.Dequeue(); 
       base.OnPropertyChanged(notification); 
      } 
     } 
    } 
} 

编辑:添加缺少的NotifyEventComparer类和实例Disposable.Create方法

public sealed class NotifyEventComparer : IEqualityComparer<PropertyChangedEventArgs> 
{ 
    public static readonly NotifyEventComparer Instance = new NotifyEventComparer(); 

    bool IEqualityComparer<PropertyChangedEventArgs>.Equals(PropertyChangedEventArgs x, PropertyChangedEventArgs y) 
    { 
     return x.PropertyName == y.PropertyName; 
    } 

    int IEqualityComparer<PropertyChangedEventArgs>.GetHashCode(PropertyChangedEventArgs obj) 
    { 
     return obj.PropertyName.GetHashCode(); 
    } 
} 

//Either use Rx to access Disposable.Create or this simple implementation will do 
public static class Disposable 
{ 
    public static IDisposable Create(Action dispose) 
    { 
     if (dispose == null) 
      throw new ArgumentNullException("dispose"); 

     return new AnonymousDisposable(dispose); 
    } 

    private sealed class AnonymousDisposable : IDisposable 
    { 
     private Action _dispose; 

     public AnonymousDisposable(Action dispose) 
     { 
      _dispose = dispose; 
     } 

     public void Dispose() 
     { 
      var dispose = Interlocked.Exchange(ref _dispose, null); 
      if (dispose != null) 
      { 
       dispose(); 
      } 
     } 
    } 
} 
+0

感谢李,反正我得到一些麻烦弄清楚什么NotifyEventComparer – Tharnadar

+0

道歉。更新为完整且独立。 –