File: System\Windows\Media\UniqueEventHelper.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// 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.
 
namespace System.Windows.Media
{
    /// <summary>
    /// Aids in making events unique. i.e. you register the same function for
    /// the same event twice, but it will only be called once for each time
    /// the event is invoked.
    ///
    /// UniqueEventHelper should only be accessed from the UI thread so that
    /// handlers that throw exceptions will crash the app, making the developer
    /// aware of the problem.
    /// </summary>
    internal sealed class UniqueEventHelper<TEventArgs> : UniqueEventHelperBase<EventHandler<TEventArgs>>
        where TEventArgs : EventArgs
    {
        /// <summary>Clones the event helper</summary>
        internal UniqueEventHelper<TEventArgs> Clone()
        {
            var ueh = new UniqueEventHelper<TEventArgs>();
            if (_delegates != null)
            {
                ueh._delegates = new Dictionary<EventHandler<TEventArgs>, int>(_delegates);
            }
 
            return ueh;
        }
 
        /// <summary>
        /// Enumerates all the keys in the hashtable, which must be EventHandlers,
        /// and invokes them.
        /// </summary>
        /// <param name="sender">The sender for the callback.</param>
        /// <param name="args">The args object to be sent by the delegate</param>
        internal void InvokeEvents(object sender, TEventArgs args)
        {
            Debug.Assert(sender != null, "Sender is null");
            foreach (EventHandler<TEventArgs> handler in CopyHandlers())
            {
                Debug.Assert(handler != null, "Event handler is null");
                handler(sender, args);
            }
        }
    }
 
    internal sealed class UniqueEventHelper : UniqueEventHelperBase<EventHandler>
    {
        /// <summary>Clones the event helper</summary>
        internal UniqueEventHelper Clone()
        {
            var ueh = new UniqueEventHelper();
            if (_delegates != null)
            {
                ueh._delegates = new Dictionary<EventHandler, int>(_delegates);
            }
 
            return ueh;
        }
 
        /// <summary>
        /// Enumerates all the keys in the hashtable, which must be EventHandlers,
        /// and invokes them.
        /// </summary>
        /// <param name="sender">The sender for the callback.</param>
        /// <param name="args">The args object to be sent by the delegate</param>
        internal void InvokeEvents(object sender, EventArgs args)
        {
            Debug.Assert(sender != null, "Sender is null");
            foreach (EventHandler handler in CopyHandlers())
            {
                Debug.Assert(handler != null, "Event handler is null");
                handler(sender, args);
            }
        }
    }
 
    internal abstract class UniqueEventHelperBase<TEventHandler> where TEventHandler : Delegate
    {
        protected Dictionary<TEventHandler, int> _delegates;
 
        /// <summary>
        /// Add the handler to the list of handlers associated with this event.
        /// If the handler has already been added, we simply increment the ref
        /// count. That way if this same handler has already been added, it
        /// won't be invoked multiple times when the event is raised.
        /// </summary>
        internal void AddEvent(TEventHandler handler)
        {
            ArgumentNullException.ThrowIfNull(handler);
 
            if (_delegates is Dictionary<TEventHandler, int> delegates)
            {
                delegates.TryGetValue(handler, out int refCount);
                delegates[handler] = refCount + 1;
            }
            else
            {
                _delegates = new Dictionary<TEventHandler, int>() { { handler, 1 } };
            }
        }
 
        /// <summary>
        /// Removed the handler from the list of handlers associated with this
        /// event. If the handler has been added multiple times (more times than
        /// it has been removed), we simply decrement its ref count.
        /// </summary>
        internal void RemoveEvent(TEventHandler handler)
        {
            ArgumentNullException.ThrowIfNull(handler);
 
            if (_delegates is Dictionary<TEventHandler, int> delegates &&
                delegates.TryGetValue(handler, out int refCount))
            {
                if (refCount == 1)
                {
                    delegates.Remove(handler);
                }
                else
                {
                    delegates[handler] = refCount - 1;
                }
            }
        }
 
        protected TEventHandler[] CopyHandlers()
        {
            if (_delegates is { Count: > 0 } delegates)
            {
                var handlers = new TEventHandler[delegates.Count];
                delegates.Keys.CopyTo(handlers, 0);
                return handlers;
            }
 
            return Array.Empty<TEventHandler>();
        }
    }
}