File: MS\Internal\Data\ValueChangedEventManager.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description: Manager for the ValueChanged event in the "weak event listener"
//              pattern.  See WeakEventTable.cs for an overview.
//
 
/****************************************************************************\
    The "value changed" pattern doesn't use events.  To listen for changes
    in a property, a client first obtains the PropertyDescriptor for that
    property, then calls the AddValueChanged method to register a callback.
    The arguments to the callback don't say which property has changed(!).
 
    The standard manager implementation doesn't work for this.  Hence this
    manager overrides and/or ignores the base class methods.
 
    This manager keeps a table of records, indexed by PropertyDescriptor.
    Each record holds the following information:
        PropertyDescriptor
        Callback method
        ListenerList
    In short, there's a separate callback method for each property.  That
    method knows which property has changed, and can ask the manager to
    deliver the "event" to the listeners that are interested in that property.
\****************************************************************************/
 
using System.Collections;               // ICollection
using System.Collections.Specialized;   // HybridDictionary
using System.ComponentModel;            // PropertyDescriptor
using System.Windows;                   // WeakEventManager
 
namespace MS.Internal.Data
{
    /// <summary>
    /// Manager for the object.ValueChanged event.
    /// </summary>
    internal class ValueChangedEventManager : WeakEventManager
    {
        #region Constructors
 
        //
        //  Constructors
        //
 
        private ValueChangedEventManager()
        {
        }
 
        #endregion Constructors
 
        #region Public Methods
 
        //
        //  Public Methods
        //
 
        /// <summary>
        /// Add a listener to the given source's event.
        /// </summary>
        public static void AddListener(object source, IWeakEventListener listener, PropertyDescriptor pd)
        {
            ArgumentNullException.ThrowIfNull(source);
            ArgumentNullException.ThrowIfNull(listener);
 
            CurrentManager.PrivateAddListener(source, listener, pd);
        }
 
        /// <summary>
        /// Remove a listener to the given source's event.
        /// </summary>
        public static void RemoveListener(object source, IWeakEventListener listener, PropertyDescriptor pd)
        {
            ArgumentNullException.ThrowIfNull(source);
            ArgumentNullException.ThrowIfNull(listener);
 
            CurrentManager.PrivateRemoveListener(source, listener, pd);
        }
 
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(object source, EventHandler<ValueChangedEventArgs> handler, PropertyDescriptor pd)
        {
            ArgumentNullException.ThrowIfNull(handler);
            if (!handler.HasSingleTarget)
                throw new NotSupportedException(SR.NoMulticastHandlers);
 
            CurrentManager.PrivateAddHandler(source, handler, pd);
        }
 
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(object source, EventHandler<ValueChangedEventArgs> handler, PropertyDescriptor pd)
        {
            ArgumentNullException.ThrowIfNull(handler);
            if (!handler.HasSingleTarget)
                throw new NotSupportedException(SR.NoMulticastHandlers);
 
            CurrentManager.PrivateRemoveHandler(source, handler, pd);
        }
 
        #endregion Public Methods
 
        #region Protected Methods
 
        //
        //  Protected Methods
        //
 
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<ValueChangedEventArgs>();
        }
 
        // The next two methods need to be defined, but they're never called.
 
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
        }
 
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
        }
 
        /// <summary>
        /// Remove dead entries from the data for the given source.   Returns true if
        /// some entries were actually removed.
        /// </summary>
        protected override bool Purge(object source, object data, bool purgeAll)
        {
            bool foundDirt = false;
 
            HybridDictionary dict = (HybridDictionary)data;
 
            if (!MS.Internal.BaseAppContextSwitches.EnableWeakEventMemoryImprovements)
            {
                // copy the keys into a separate array, so that later on
                // we can change the dictionary while iterating over the keys
                ICollection ic = dict.Keys;
                PropertyDescriptor[] keys = new PropertyDescriptor[ic.Count];
                ic.CopyTo(keys, 0);
 
                for (int i = keys.Length - 1; i >= 0; --i)
                {
                    // for each key, remove dead entries in its list
                    bool removeList = purgeAll || source == null;
 
                    ValueChangedRecord record = (ValueChangedRecord)dict[keys[i]];
 
                    if (!removeList)
                    {
                        if (record.Purge())
                            foundDirt = true;
 
                        removeList = record.IsEmpty;
                    }
 
                    // if there are no more entries, remove the key
                    if (removeList)
                    {
                        record.StopListening();
                        if (!purgeAll)
                        {
                            dict.Remove(keys[i]);
                        }
                    }
                }
 
#if WeakEventTelemetry
                LogAllocation(ic.GetType(), 1, 12);                     // dict.Keys - Hashtable+KeyCollection
                LogAllocation(typeof(String[]), 1, 12+ic.Count*4);      // keys
#endif
            }
            else
            {
                Debug.Assert(_toRemove.Count == 0, "to-remove list should be empty");
 
                // enumerate the dictionary using IDE explicitly rather than
                // foreach, to avoid allocating temporary DictionaryEntry objects
                IDictionaryEnumerator ide = dict.GetEnumerator() as IDictionaryEnumerator;
                while (ide.MoveNext())
                {
                    // for each key, remove dead entries in its list
                    bool removeList = purgeAll || source == null;
 
                    ValueChangedRecord record = (ValueChangedRecord)ide.Value;
 
                    if (!removeList)
                    {
                        if (record.Purge())
                            foundDirt = true;
 
                        removeList = record.IsEmpty;
                    }
 
                    // if there are no more entries, remove the key
                    if (removeList)
                    {
                        record.StopListening();
                        if (!purgeAll)
                        {
                            _toRemove.Add((PropertyDescriptor)ide.Key);
                        }
                    }
                }
 
                // do the actual removal (outside the dictionary iteration)
                if (_toRemove.Count > 0)
                {
                    foreach (PropertyDescriptor key in _toRemove)
                    {
                        dict.Remove(key);
                    }
                    _toRemove.Clear();
                    _toRemove.TrimExcess();
                }
 
#if WeakEventTelemetry
                Type enumeratorType = ide.GetType();
                if (enumeratorType.Name.IndexOf("NodeEnumerator") >= 0)
                {
                    LogAllocation(enumeratorType, 1, 24);                    // ListDictionary+NodeEnumerator
                }
                else
                {
                    LogAllocation(enumeratorType, 1, 36);                    // Hashtable+HashtableEnumerator
                }
#endif
            }
 
 
            // if there are no more listeners at all, remove the entry from
            // the main table
            if (dict.Count == 0)
            {
                foundDirt = true;
                if (source != null)     // source may have been GC'd
                {
                    this.Remove(source);
                }
            }
 
            return foundDirt;
        }
 
        #endregion Protected Methods
 
        #region Private Properties
 
        //
        //  Private Properties
        //
 
        // get the event manager for the current thread
        private static ValueChangedEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(ValueChangedEventManager);
                ValueChangedEventManager manager = (ValueChangedEventManager)GetCurrentManager(managerType);
 
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new ValueChangedEventManager();
                    SetCurrentManager(managerType, manager);
                }
 
                return manager;
            }
        }
 
        #endregion Private Properties
 
        #region Private Methods
 
        //
        //  Private Methods
        //
 
        // Add a listener to the given property
        private void PrivateAddListener(object source, IWeakEventListener listener, PropertyDescriptor pd)
        {
            Debug.Assert(listener != null && source != null && pd != null,
                "Listener, source, and pd of event cannot be null");
            AddListener(source, pd, listener, null);
        }
 
        // Remove a listener to the given property
        private void PrivateRemoveListener(object source, IWeakEventListener listener, PropertyDescriptor pd)
        {
            Debug.Assert(listener != null && source != null && pd != null,
                "Listener, source, and pd of event cannot be null");
            RemoveListener(source, pd, listener, null);
        }
 
        // Add a handler for the given property
        private void PrivateAddHandler(object source, EventHandler<ValueChangedEventArgs> handler, PropertyDescriptor pd)
        {
            AddListener(source, pd, null, handler);
        }
 
        // Remove a handler for the given property
        private void PrivateRemoveHandler(object source, EventHandler<ValueChangedEventArgs> handler, PropertyDescriptor pd)
        {
            RemoveListener(source, pd, null, handler);
        }
 
        private void AddListener(object source, PropertyDescriptor pd, IWeakEventListener listener, EventHandler<ValueChangedEventArgs> handler)
        {
            using (WriteLock)
            {
                HybridDictionary dict = (HybridDictionary)this[source];
 
                if (dict == null)
                {
                    // no entry in the hashtable - add a new one
                    dict = new HybridDictionary();
 
                    this[source] = dict;
                }
 
                ValueChangedRecord record = (ValueChangedRecord)dict[pd];
 
                if (record == null)
                {
                    // no entry in the dictionary - add a new one
                    record = new ValueChangedRecord(this, source, pd);
 
                    dict[pd] = record;
                }
 
                // add a listener to the list
                record.Add(listener, handler);
 
                // schedule a cleanup pass
                ScheduleCleanup();
            }
        }
 
        private void RemoveListener(object source, PropertyDescriptor pd, IWeakEventListener listener, EventHandler<ValueChangedEventArgs> handler)
        {
            using (WriteLock)
            {
                HybridDictionary dict = (HybridDictionary)this[source];
 
                if (dict != null)
                {
                    ValueChangedRecord record = (ValueChangedRecord)dict[pd];
 
                    if (record != null)
                    {
                        // remove a listener from the list
                        record.Remove(listener, handler);
 
                        // when the last listener goes away, remove the list
                        if (record.IsEmpty)
                        {
                            dict.Remove(pd);
                        }
                    }
 
                    if (dict.Count == 0)
                    {
                        Remove(source);
                    }
                }
            }
        }
 
        #endregion Private Methods
 
        List<PropertyDescriptor> _toRemove = new List<PropertyDescriptor>();
 
        #region ValueChangedRecord
 
        private class ValueChangedRecord
        {
            public ValueChangedRecord(ValueChangedEventManager manager, object source, PropertyDescriptor pd)
            {
                // keep a strong reference to the source.  Normally we avoid this, but
                // it's OK here since its scope is exactly the same as the strong reference
                // held by the PD:  begins with pd.AddValueChanged, ends with
                // pd.RemoveValueChanged.   This ensures that we _can_ call RemoveValueChanged
                // even in cases where the source implements value-semantics (which
                // confuses the PD - see 795205).
                _manager = manager;
                _source = source;
                _pd = pd;
                _eventArgs = new ValueChangedEventArgs(pd);
 
                pd.AddValueChanged(source, new EventHandler(OnValueChanged));
            }
 
            public bool IsEmpty
            {
                get
                {
                    bool result = _listeners.IsEmpty;
                    if (!result && HasIgnorableListeners)
                    {
                        // if all the remaining listeners are "ignorable",
                        // treat the list as empty
                        result = true;
                        for (int i = 0, n = _listeners.Count; i < n; ++i)
                        {
                            Listener listener = _listeners.GetListener(i);
                            if (!IsIgnorable(listener.Target))
                            {
                                result = false;
                                break;
                            }
                        }
                    }
                    return result;
                }
            }
 
            // add a listener
            public void Add(IWeakEventListener listener, EventHandler<ValueChangedEventArgs> handler)
            {
                // make sure list is ready for writing
                ListenerList list = _listeners;
                if (ListenerList.PrepareForWriting(ref list))
                    _listeners = (ListenerList<ValueChangedEventArgs>)list;
 
                if (handler != null)
                {
                    _listeners.AddHandler(handler);
                    if (!HasIgnorableListeners && IsIgnorable(handler.Target))
                    {
                        HasIgnorableListeners = true;
                    }
                }
                else
                {
                    _listeners.Add(listener);
                }
            }
 
            // remove a listener
            public void Remove(IWeakEventListener listener, EventHandler<ValueChangedEventArgs> handler)
            {
                // make sure list is ready for writing
                ListenerList list = _listeners;
                if (ListenerList.PrepareForWriting(ref list))
                    _listeners = (ListenerList<ValueChangedEventArgs>)list;
 
                if (handler != null)
                {
                    _listeners.RemoveHandler(handler);
                }
                else
                {
                    _listeners.Remove(listener);
                }
 
                // when the last listener goes away, remove the callback
                if (IsEmpty)
                {
                    StopListening();
                }
            }
 
            // purge dead entries
            public bool Purge()
            {
                ListenerList list = _listeners;
                if (ListenerList.PrepareForWriting(ref list))
                    _listeners = (ListenerList<ValueChangedEventArgs>)list;
 
                return _listeners.Purge();
            }
 
            // remove the callback from the PropertyDescriptor
            public void StopListening()
            {
                if (_source != null)
                {
                    _pd.RemoveValueChanged(_source, new EventHandler(OnValueChanged));
                    _source = null;
                }
            }
 
            // forward the ValueChanged event to the listeners
            private void OnValueChanged(object sender, EventArgs e)
            {
                // mark the list of listeners "in use"
                using (_manager.ReadLock)
                {
                    _listeners.BeginUse();
                }
 
                // deliver the event, being sure to undo the effect of BeginUse().
                try
                {
                    _manager.DeliverEventToList(sender, _eventArgs, _listeners);
                }
                finally
                {
                    _listeners.EndUse();
                }
            }
 
            // Some listeners are used only for internal bookkeeping.  These
            // shouldn't count as real listeners for the purpose of deciding
            // if anyone is still listening to the change event;  otherwise
            // we'd never release the event and we'd have a memory leak .
            // Call such listeners "ignorable";  the add, remove, and purge logic has
            // special cases for ignorable listeners.  Ignorable listeners are
            // rare, so we optimize for their absence.
 
            private bool HasIgnorableListeners { get; set; }
 
            private bool IsIgnorable(object target)
            {
                // ValueTable listens for changes from malfeasant ADO properties
                return (target is MS.Internal.Data.ValueTable);
            }
 
            PropertyDescriptor _pd;
            ValueChangedEventManager _manager;
            object _source;
            ListenerList<ValueChangedEventArgs> _listeners = new ListenerList<ValueChangedEventArgs>();
            ValueChangedEventArgs _eventArgs;
        }
 
        #endregion ValueChangedRecord
    }
 
    #region ValueChangedEventArgs
 
    internal class ValueChangedEventArgs : EventArgs
    {
        internal ValueChangedEventArgs(PropertyDescriptor pd)
        {
            _pd = pd;
        }
 
        internal PropertyDescriptor PropertyDescriptor
        {
            get { return _pd; }
        }
 
        private PropertyDescriptor _pd;
    }
    #endregion ValueChangedEventArgs
}