是否可以减少PropertyChanged事件的委托实例数量?

时间:2015-02-14 16:53:26

标签: c#

例如,如果我有一个实现PropertyChanged事件INotifyPropertyChanged接口的类型,并创建该类型的1万个实例并在其他地方注册事件处理程序。然后将为每个事件处理程序创建匹配数量的委托实例。我想减少记忆足迹,但我担心不可能避免这种情况。

我取消注册事件处理程序,并在对象处理时最终清理内存。但是,我不喜欢为每个事件处理程序创建的许多代理实例。

以下是代码:

public class MyCollectionPropertyObserver : IDisposable
{
    #region Fields

    private IObservableList _sourceCollection;

    private readonly SynchronizedObservableHashSet<string> _propNameFilter =
        new SynchronizedObservableHashSet<string>();

    #endregion

    #region Events

    public event EventHandler<PropertyObservedInfoEventArgs> ChangeDetected;

    #endregion

    #region Constructor

    public MyCollectionPropertyObserver(IObservableList collection)
    {
        _sourceCollection = collection;
        _sourceCollection.CollectionChanged += WeakEventHandler.Wrap(CollectionChanged, eh => _sourceCollection.CollectionChanged -= eh);

        Subscribe(_sourceCollection);
    }

    #endregion

    #region Properties

    public IObservableList SourceCollection
    {
        get { return _sourceCollection; }
    }

    public SynchronizedObservableHashSet<string> PropertyNameFilters
    {
        get { return _propNameFilter; }
    }

    #endregion

    #region Event Handlers

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                Subscribe(e.NewItems);
                break;

            case NotifyCollectionChangedAction.Remove:
                Unsubscribe(e.OldItems);
                break;

            case NotifyCollectionChangedAction.Replace:
                Unsubscribe(e.OldItems);
                Subscribe(e.NewItems);
                break;

            case NotifyCollectionChangedAction.Reset:
                Unsubscribe(_subscribedItems.ToList());
                Subscribe(_sourceCollection);
                break;
        }

        RaiseChangeDetected(new PropertyObservedInfoEventArgs(e.Action, e.NewItems, e.OldItems));
    }

    private void PropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (!IsFilteredProperty(args.PropertyName)) return;
        RaiseChangeDetected(new PropertyObservedInfoEventArgs(sender, args.PropertyName));
    }

    private void InstanceChanged(object sender, InstanceChangedEventArgs args)
    {
        if (!IsFilteredProperty(args.ChangedProperties)) return;
        RaiseChangeDetected(new PropertyObservedInfoEventArgs(sender, args.ChangedProperties));
    }

    #endregion

    #region Methods

    private bool IsFilteredProperty(string propertyName)
    {
        //NOTE: It is important to perform Contains check before Count == 0.  Count locks, and typically there are filtered properties
        return PropertyNameFilters.Contains(propertyName) || PropertyNameFilters.Count == 0;
    }

    private bool IsFilteredProperty(IEnumerable<string> propertyNames)
    {
        if (propertyNames == null) return false;
        //NOTE: It is important to perform Overlaps check before Count == 0.  Count locks, and typically there are filtered properties
        return PropertyNameFilters.Overlaps(propertyNames) || PropertyNameFilters.Count == 0;
    }

    private void Subscribe(IEnumerable entities)
    {
        if (entities == null) return;

        foreach (var entity in entities)
        {
            Subscribe(entity);
        }
    }

    private readonly SynchronizedObservableHashSet<object> _subscribedItems =
        new SynchronizedObservableHashSet<object>();

    private void Subscribe(object entity)
    {
        if (entity == null) return;
        if (_subscribedItems.Contains(entity))
            return;

        _subscribedItems.Add(entity);

        var propChange = entity as INotifyPropertyChanged;
        if (propChange != null)
            propChange.PropertyChanged += PropertyChanged;

        var instChanged = entity as INotifyInstanceChanged;
        if (instChanged != null)
            instChanged.InstanceChanged += InstanceChanged;
    }

    private void Unsubscribe(IEnumerable entities)
    {
        if (entities == null) return;
        foreach (var entity in entities)
        {
            Unsubscribe(entity);
        }
    }

    private void Unsubscribe(object entity)
    {
        if (entity == null) return;

        _subscribedItems.Remove(entity);
        var propChanged = entity as INotifyPropertyChanged;
        if (propChanged != null)
            propChanged.PropertyChanged -= PropertyChanged;

        var instChanged = entity as INotifyInstanceChanged;
        if (instChanged != null)
            instChanged.InstanceChanged -= InstanceChanged;
    }

    private void RaiseChangeDetected(PropertyObservedInfoEventArgs message)
    {
        var handler = Volatile.Read(ref ChangeDetected);
        if (handler == null) return;
        handler(this, message);
    }

    private void CleanUp()
    {
        if (_sourceCollection == null) return;
        _sourceCollection.CollectionChanged -= CollectionChanged;
        _sourceCollection = null;
        Unsubscribe(_subscribedItems.ToList());
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
            CleanUp();
    }

    #endregion
}

1 个答案:

答案 0 :(得分:2)

如果事件订阅之间存在一些冗余,那么是的,您可以大大减少委托对象的数量。例子:

10000个来源,一个接收器

浪费,因为从方法组到委托的每次转换都会创建一个新的委托对象,即使目标对象和目标方法在所有对象中完全相同:

foreach( source in source_list )
{
    source.PropertyChanged += this.ItHappened;
}

更好:

PropertyChangedEventHandler common = this.ItHappened;
foreach( source in source_list )
{
    source.PropertyChanged += common;
}

一个来源,10000汇

浪费,因为虽然为每个接收器对象调用相同的方法,但委托存储目标和方法信息:

foreach( sink in sink_list )
{
    source.PropertyChanged += sink.ItHappened;
}

更好:

source.PropertyChanged += delegate(sender, args) {
   foreach ( sink in sink_list ) {
       sink.ItHappened(sender, args);
   }
}

现在代码已添加到问题中,我可以向您展示如何实现我的建议。变化

public MyCollectionPropertyObserver(IObservableList collection)
{
    _sourceCollection = collection;
    _sourceCollection.CollectionChanged += WeakEventHandler.Wrap(CollectionChanged, eh => _sourceCollection.CollectionChanged -= eh);

    Subscribe(_sourceCollection);
}

private void Subscribe(object entity)
{
    if (entity == null) return;
    if (_subscribedItems.Contains(entity))
        return;

    _subscribedItems.Add(entity);

    var propChange = entity as INotifyPropertyChanged;
    if (propChange != null)
        propChange.PropertyChanged += PropertyChanged; // creates a new delegate object, wasteful!

    var instChanged = entity as INotifyInstanceChanged;
    if (instChanged != null)
        instChanged.InstanceChanged += InstanceChanged; // same problem, wasteful!
}

private readonly PropertyChangedEventHandler reusablePropertyChangeDelegate;
private readonly InstanceChangedEventHandler reusableInstanceChangedDelegate;

public MyCollectionPropertyObserver(IObservableList collection)
{
    reusablePropertyChangeDelegate = PropertyChanged;
    reusableInstanceChangeDelegate = InstanceChanged;
    _sourceCollection = collection;
    _sourceCollection.CollectionChanged += WeakEventHandler.Wrap(CollectionChanged, eh => _sourceCollection.CollectionChanged -= eh);

    Subscribe(_sourceCollection);
}

private void Subscribe(object entity)
{
    if (entity == null) return;
    if (_subscribedItems.Contains(entity))
        return;

    _subscribedItems.Add(entity);

    var propChange = entity as INotifyPropertyChanged;
    if (propChange != null)
        propChange.PropertyChanged += reusablePropertyChangeDelegate;

    var instChanged = entity as INotifyInstanceChanged;
    if (instChanged != null)
        instChanged.InstanceChanged += reusableInstanceChangeDelegate;
}

您还应该更改Unsubscribe以使用缓存的实例。