Search code examples
c#eventsc#-4.0extension-methodspresenter-first

Subscribe/Unsubscribe (add/remove) to event inside Extension Method


I want to create a fluent extension method for subscribing to (and less importantly unsubscribing from) an event. That is an extension with the usage .RespondBy(Method) in place of a += new Eventhandler(Method)

I want to do this: object.WhenSomethingChanges.RespondBy(DoingThisOtherThing);

Instead of this: object.WhenSomethingChanges += new EventHandler(DoingThisOtherThing);

I did a bunch of googling and while I didn't exactly grasp the intricate details, I do understand now that this has to do with whether you are accessing the local field or the public event.

With that said, I am simply interested in "how" this can be done and not concerned with "why" my first attempt hasn't worked. Failing a workaround, at least a definitive "You can not do this... at all, ever." would also be useful information...

CommuncationsStatusPresenter (Image)

Image of attempted usage, does not compile

CommuncationsStatusPresenter (Code)

using System;
using InspectionStation.Models;
using InspectionStation.Views;
using MachineControl.OPC;

namespace InspectionStation.Presenters
{
    public class CommuncationsStatusPresenter
    {
        // Fields
        private ICommunicationsModel m_model;
        private ICommunicationsView m_view;

        // Constructor
        public CommuncationsStatusPresenter
            (ICommunicationsModel p_model, ICommunicationsView p_view)
        {
            m_model = p_model;
            m_view = p_view;
            HookEvents();
        }
        private void HookEvents()
        {
            m_model
                .When_Communications_Pulses_Heartbeat
                .RespondBy(Setting_the_state_of_an_Indicator);
        }

        // Eventhandler
        void Setting_the_state_of_an_Indicator(Tag sender, EventArgs e)
        {
            bool State = sender.BooleanValue;
            m_view.Set_Communications_Status_Indicator = State;
        }
    }
}

RespondBy

using System;

namespace Common.Extensions
{
    public static partial class ExtensionMethods
    {
        public static
            void RespondBy<TSender, TEventArgs>(this
            GenericEventHandler<TSender, TEventArgs> p_event,
            GenericEventHandler<TSender, TEventArgs> p_handler
            ) where TEventArgs : EventArgs
        {
            p_event += new GenericEventHandler<TSender, TEventArgs>(p_handler);
        }
    }
}

GenericEventHandler

using System;

namespace Common
{
    [SerializableAttribute]
    public delegate void GenericEventHandler<TSender, TEventArgs>
        (TSender sender, TEventArgs e)
        where TEventArgs : EventArgs;
}

ICommunicationsModel

using System;
using Common;
using MachineControl.OPC;

namespace InspectionStation.Models
{
    public interface ICommunicationsModel
    {
        event GenericEventHandler<Tag, EventArgs>
            When_Communications_Pulses_Heartbeat;
    }
}

ICommunicationsView

namespace InspectionStation.Views
{
    public interface ICommunicationsView
    {
        bool Set_Communications_Status_Indicator { set; }
    }
}

Solution

  • Because the C# compiler strictly requires that events used outside the class must be followed by a += or -=, the attach and detach commands for handlers, it won't be possible to extend and use this outside of the class.

    However, if you're willing to build more specific methods on the class itself like:

    object.RespondWhenSomethingChangesBy(DoingThisOtherThing);
    

    then you could, inside of that method, leverage the extension method because that class defined the event. I know it means you'd be building a lot of boiler plate code for the events, but if this is what you really want then the aforementioned might be found to be even more streamlined than:

    object.WhenSomethingChanges.RespondBy(DoingThisOtherThing);
    

    I do fully understand your position here, and hope that Microsoft chooses to allow events to be extended in the future (I can think of some interesting reasons I'd use it), but until then I guess we'll just have to work around it. Honestly, I'm confident there are some very good reasons it wasn't implemented in the first place, Microsoft does a pretty good job of thinking through these things.