File: WeakEventManager.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
 
using static System.String;
namespace Microsoft.Maui
{
	/// <include file="../docs/Microsoft.Maui/WeakEventManager.xml" path="Type[@FullName='Microsoft.Maui.WeakEventManager']/Docs/*" />
	public class WeakEventManager
	{
		readonly Dictionary<string, List<Subscription>> _eventHandlers = new(StringComparer.Ordinal);
 
		/// <include file="../docs/Microsoft.Maui/WeakEventManager.xml" path="//Member[@MemberName='AddEventHandler'][1]/Docs/*" />
		public void AddEventHandler<TEventArgs>(EventHandler<TEventArgs> handler, [CallerMemberName] string eventName = "")
			where TEventArgs : EventArgs
		{
			if (IsNullOrEmpty(eventName))
				throw new ArgumentNullException(nameof(eventName));
 
			if (handler == null)
				throw new ArgumentNullException(nameof(handler));
 
			AddEventHandler(eventName, handler.Target, handler.GetMethodInfo());
		}
 
		public void AddEventHandler(Delegate? handler, [CallerMemberName] string eventName = "")
		{
			if (IsNullOrEmpty(eventName))
				throw new ArgumentNullException(nameof(eventName));
 
			if (handler == null)
				throw new ArgumentNullException(nameof(handler));
 
			AddEventHandler(eventName, handler.Target, handler.GetMethodInfo());
		}
 
		/// <include file="../docs/Microsoft.Maui/WeakEventManager.xml" path="//Member[@MemberName='HandleEvent']/Docs/*" />
		public void HandleEvent(object? sender, object? args, string eventName)
		{
			var toRaise = new List<(object? subscriber, MethodInfo handler)>();
			var toRemove = new List<Subscription>();
 
			if (_eventHandlers.TryGetValue(eventName, out List<Subscription>? target))
			{
				for (int i = 0; i < target.Count; i++)
				{
					Subscription subscription = target[i];
					bool isStatic = subscription.Subscriber == null;
					if (isStatic)
					{
						// For a static method, we'll just pass null as the first parameter of MethodInfo.Invoke
						toRaise.Add((null, subscription.Handler));
						continue;
					}
 
					object? subscriber = subscription.Subscriber?.Target;
 
					if (subscriber == null)
						// The subscriber was collected, so there's no need to keep this subscription around
						toRemove.Add(subscription);
					else
						toRaise.Add((subscriber, subscription.Handler));
				}
 
				for (int i = 0; i < toRemove.Count; i++)
				{
					Subscription subscription = toRemove[i];
					target.Remove(subscription);
				}
			}
 
			for (int i = 0; i < toRaise.Count; i++)
			{
				(var subscriber, var handler) = toRaise[i];
				handler.Invoke(subscriber, new[] { sender, args });
			}
		}
 
		/// <include file="../docs/Microsoft.Maui/WeakEventManager.xml" path="//Member[@MemberName='RemoveEventHandler'][1]/Docs/*" />
		public void RemoveEventHandler<TEventArgs>(EventHandler<TEventArgs> handler, [CallerMemberName] string eventName = "")
			where TEventArgs : EventArgs
		{
			if (IsNullOrEmpty(eventName))
				throw new ArgumentNullException(nameof(eventName));
 
			if (handler == null)
				throw new ArgumentNullException(nameof(handler));
 
			RemoveEventHandler(eventName, handler.Target, handler.GetMethodInfo());
		}
 
		public void RemoveEventHandler(Delegate? handler, [CallerMemberName] string eventName = "")
		{
			if (IsNullOrEmpty(eventName))
				throw new ArgumentNullException(nameof(eventName));
 
			if (handler == null)
				throw new ArgumentNullException(nameof(handler));
 
			RemoveEventHandler(eventName, handler.Target, handler.GetMethodInfo());
		}
 
		void AddEventHandler(string eventName, object? handlerTarget, MethodInfo methodInfo)
		{
			if (!_eventHandlers.TryGetValue(eventName, out List<Subscription>? targets))
			{
				targets = new List<Subscription>();
				_eventHandlers.Add(eventName, targets);
			}
 
			if (handlerTarget == null)
			{
				// This event handler is a static method
				targets.Add(new Subscription(null, methodInfo));
				return;
			}
 
			targets.Add(new Subscription(new WeakReference(handlerTarget), methodInfo));
		}
 
		void RemoveEventHandler(string eventName, object? handlerTarget, MemberInfo methodInfo)
		{
			if (!_eventHandlers.TryGetValue(eventName, out List<Subscription>? subscriptions))
				return;
 
			for (int n = subscriptions.Count - 1; n >= 0; n--)
			{
				Subscription current = subscriptions[n];
 
				if (current.Subscriber != null && !current.Subscriber.IsAlive)
				{
					// If not alive, remove and continue
					subscriptions.RemoveAt(n);
					continue;
				}
 
				if (current.Subscriber?.Target == handlerTarget && current.Handler.Name == methodInfo.Name)
				{
					// Found the match, we can break
					subscriptions.RemoveAt(n);
					break;
				}
			}
		}
 
		readonly struct Subscription : IEquatable<Subscription>
		{
			/// <include file="../docs/Microsoft.Maui/WeakEventManager.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
			public Subscription(WeakReference? subscriber, MethodInfo handler)
			{
				Subscriber = subscriber;
				Handler = handler ?? throw new ArgumentNullException(nameof(handler));
			}
 
			public readonly WeakReference? Subscriber;
			public readonly MethodInfo Handler;
 
			public bool Equals(Subscription other) => Subscriber == other.Subscriber && Handler == other.Handler;
 
			public override bool Equals(object? obj) => obj is Subscription other && Equals(other);
 
			public override int GetHashCode() => Subscriber?.GetHashCode() ?? 0 ^ Handler.GetHashCode();
		}
	}
}