Wednesday, April 17, 2019

How to detect if an event is already subscribed

Sometimes it's quite nice to know if you have already subscribed to an event or not. Of course you can build your own memory container, but essentially this is not needed, at least, for instance events. And sometimes you cannot add memory because you have to interact with code you didn't wrote and you cannot change it.

You can use the following methods I wrote to check against existing event subscription. There are some overloads for more or less accurate subscription check.

Note: This only works with instance events and instance event subscription. If the event is subscribed or provided through a static method, this code can't help you since there is no subscriber or/and provider instance to check against.


using System;
using System.Linq;
using System.Reflection;

namespace AnyNamespace
{
    /// <summary>
    /// Provides extension methods for object types
    /// </summary>
    public static class ObjectExtender
    {
        /// <summary>
        /// Checks if an event subscription for potential subscriber
        /// and its subscription method definition exists
        /// </summary>
        /// <param name="eventOwnerInstance">
        /// The object containing the subscriptions
        /// </param>
        /// <param name="eventOwnerType">
        /// The type which defines the event
        /// </param>
        /// <param name="eventName">
        /// The name of the event on the type which defines it
        /// </param>
        /// <param name="potentialSubscriber">
        /// The potential subscriber to check
        /// </param>
        /// <param name="subscriptionMethodInfo">
        /// The subscription method definition to use as search filter
        /// </param>
        /// <returns>
        /// <c>True</c> if the <c>potentialSubscriber</c> has at least one
        /// subscription with the given <c>subscriptionMethodInfo</c>
        /// </returns>
        static public bool EventSubscriptionExists(this object eventOwnerInstance,
            Type eventOwnerType,
                string eventName,
                    object potentialSubscriber,
                        MethodInfo subscriptionMethodInfo)
        {
            return EventSubscriptionExists(eventOwnerType,
                eventOwnerInstance,
                    eventName,
                        potentialSubscriber,
                            null,
                                subscriptionMethodInfo);
        }

        /// <summary>
        /// Checks if an event subscription for potential subscriber
        /// and its subscription method name definition exists
        /// </summary>
        /// <param name="eventOwnerInstance">
        /// The object containing the subscriptions
        /// </param>
        /// <param name="eventOwnerType">
        /// The type which defines the event
        /// </param>
        /// <param name="eventName">
        /// The name of the event on the type which defines it
        /// </param>
        /// <param name="potentialSubscriber">
        /// The potential subscriber to check
        /// </param>
        /// <param name="subscriptionMethodName">
        /// The subscription method name to use as search filter
        /// </param>
        /// <returns>
        /// <c>True</c> if the <c>potentialSubscriber</c> has at least
        /// one subscription with the given <c>subscriptionMethodName</c>
        /// </returns>
        /// <remarks>
        /// This is a more weak form of method overload
        /// (object, Type, string, object, MethodInfo)
        /// since it just uses a method name instead a method definition
        /// to filter the subscriptions
        /// </remarks>
        static public bool EventSubscriptionExists(this object eventOwnerInstance,
            Type eventOwnerType,
                string eventName,
                    object potentialSubscriber,
                        string subscriptionMethodName)
        {
            return EventSubscriptionExists(eventOwnerType,
                eventOwnerInstance,
                    eventName,
                        potentialSubscriber,
                            subscriptionMethodName, null);
        }

        /// <summary>
        /// Checks if an event subscription for potential subscriber exists
        /// </summary>
        /// <param name="eventOwnerInstance">
        /// The object containing the subscriptions
        /// </param>
        /// <param name="eventOwnerType">
        /// The type which defines the event
        /// </param>
        /// <param name="eventName">
        /// The name of the event on the type which defines it
        /// </param>
        /// <param name="potentialSubscriber">
        /// The potential subscriber to check
        /// </param>
        /// <returns>
        /// <c>True</c> if the <c>potentialSubscriber</c> has
        /// at least one subscription
        /// </returns>
        static public bool EventSubscriptionExists(this object eventOwnerInstance,
            Type eventOwnerType,
                string eventName,
                    object potentialSubscriber)
        {
            return EventSubscriptionExists(eventOwnerType,
                eventOwnerInstance,
                    eventName,
                        potentialSubscriber,
                            null,
                                null);
        }

        static bool EventSubscriptionExists(Type eventOwnerType,
            object eventOwnerInstance,
                string eventName,
                    object potentialSubscriber,
                        string subscriptionMethodName,
                            MethodInfo subscriptionMethodInfo)
        {
            bool ret = false;

            var ownerType = eventOwnerType;

            if (ownerType.GetEvents().FirstOrDefault(x => x.Name == eventName)
                is EventInfo eventInfo)
            {
                var fieldInfoFromEvent = ownerType.GetField(eventInfo.Name,
                                    BindingFlags.NonPublic
                                        | BindingFlags.Instance
                                            | BindingFlags.GetField);

                var valueFromField = fieldInfoFromEvent.GetValue(eventOwnerInstance);

                if (valueFromField is Delegate eventDelegate)
                {
                    ret = eventDelegate.GetInvocationList()
                        .Any(x => (potentialSubscriber == null
                            || potentialSubscriber.Equals(x.Target))
                                && (subscriptionMethodName == null
                                    || x.Method.Name == subscriptionMethodName)
                                        && (subscriptionMethodInfo == null
                                            || subscriptionMethodInfo.Equals(x.Method)));
                }
                // else: there are no subscribers at all
            }
            // else: no event with given name exists (consider to throw an exception here)

            return ret;
        }
    }
}


Here is a demo how it works:


using System;

namespace AnyNamespace
{
    class EventProvider
    {
        public event EventHandler Event;
    }

    class EventConsumer
    {

        private void EventCatcher(object sender, EventArgs e)
        {
            // foo
            ;
        }

        public void SubscribeIfNotIsAlready(EventProvider eventProvider)
        {
            // less accurate check
            if (eventProvider.EventSubscriptionExists(eventProvider.GetType(),
                nameof(EventProvider.Event), this))
            {
                Console.WriteLine("The eventConsumer has subscribed to the eventProvider, "
                    + "we don't know with which method, but a subscription exists ...");
            }

            // more accurate check
            if (eventProvider.EventSubscriptionExists(eventProvider.GetType(),
                nameof(EventProvider.Event), this, nameof(EventCatcher)))
            {
                Console.WriteLine("The eventConsumer has even subscribed to the eventProvider "
                    + "with a method named 'EventCatcher', so we don't need to do it again ...");
            }
            else
            {
                Console.WriteLine("There was no subscription by 'EventCatcher' for this event, "
                    +"we add now ..");
                eventProvider.Event += EventCatcher;
            }
        }
    }

    class Program
    {
        static EventProvider eventProvider = new EventProvider();
        static EventConsumer eventConsumer = new EventConsumer();

        static void Main(string[] args)
        {
            Console.WriteLine("1. Imagine this code is not known by us and it conditionally "
                + "adds a subscription to an event.");

            eventConsumer.SubscribeIfNotIsAlready(eventProvider);

            Console.WriteLine("2. Now we continue without knowning if we still need to add "
                + "a subscription to the specified event.");

            DoSomething();

            Console.ReadKey();
        }

        static void DoSomething()
        {
            /* assume, that we don't know if the event is already subscribed:
             * maybe, because the class is partial or, or, or whyever
             */
            eventConsumer.SubscribeIfNotIsAlready(eventProvider);
        }
    }
}