Sunday, November 17, 2019

Extension methods: what if ...

Say, you have built your own extension method on some .NET-Framework types. However, in the future Microsoft comes up with exact such methods on the specific types itself: Means same method name, same parameter profile (except the this parameter) as your extension method . What happens now?

Here's the answer
  • There will be no compling error at all, I did not even see a compiler warning
  • Visual Studio Intellisense will show you both methods
  • At runtime, the member method on the type is executed - not your extension method

Conclusion
  • Extension methods can exist concurrent to member methods with same profile
  • Extension methods acts as substitude or shadow only

Considerations
  • If you have extension methods which become competetive to member methods, hopefully they do the same, otherwise you will experience quite ugly surprises.
  • If you build extension methods which you assume could became baked into .NET, look out for it's arrival, compare functionality and get rid of your extension method.
  • Name your extension methods correctly and avoid generic names to reduce the risk of running in dilema. Although I don't like to use name prefixes, it really would help.
  • Do not trust reference searches in case extension method and member method exists in competetive situation.

Personal thoughts
I love extension methods. Although I find it quite ugly, that you can compile even with concurrent method situation. I hope future compilers will have an option to complain such situation.

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);
        }
    }
}