Tom Smykowski
Tom Smykowski

Reputation: 26089

How to pass an event to a method?

I would like to create a method that takes an event as an argument and adds eventHandler to it to handle it properly. Like this:

I have two events:

public event EventHandler Click;
public event EventHandler Click2;

Now I would like to pass a particular event to my method like this (pseudocode):

public AttachToHandleEvent(EventHandler MyEvent)
{
    MyEvent += Item_Click;
}

private void Item_Click(object sender, EventArgs e)
{
    MessageBox.Show("lalala");
}

ToolStripMenuItem tool = new ToolStripMenuItem();
AttachToHandleEvent(tool.Click);

Is it possible?

I've noticed that this code worked fine, and returned to my project and noticed that when I pass an event declared in my class, it works, but when I pass event from other class it still does not work.

What I get is this error:

The event 'System.Windows.Forms.ToolStripItem.Click' can only appear on the left hand side of += or -=

Upvotes: 28

Views: 40333

Answers (9)

Kyle Shrader
Kyle Shrader

Reputation: 952

One approach I haven't seen here would be to create an object which has delegates for subscribe and unsubscribe. Here is a complete example program.

class Program
{
    private event EventHandler<EventArgs> eventHandler;

    public static void Main(string[] args)
    {
        Program program = new Program();
        Thing thing = new Thing(new EventWrapper<EventArgs>(
            delegate(EventHandler<EventArgs> handler) { program.eventHandler += handler; },
            delegate(EventHandler<EventArgs> handler) { program.eventHandler -= handler; }
        ));

        // events are fired
        program.eventHandler?.Invoke(program, EventArgs.Empty);

        thing.Unsubscribe();
    }
}

class Thing
{
    private readonly Action<EventHandler<EventArgs>> _unsubscribeEventHandler;

    public Thing(EventWrapper<EventArgs> eventHandler)
    {
        this._unsubscribeEventHandler = eventHandler.Unsubscribe;
        eventHandler.Subscribe?.Invoke(OnEvent);
        Console.WriteLine("subscribed");
    }

    private void OnEvent(object? sender, EventArgs e)
    {
        Console.WriteLine("event fired");
    }

    public void Unsubscribe()
    {
        _unsubscribeEventHandler?.Invoke(OnEvent);
        Console.WriteLine("unsubscribed");
    }
}

class EventWrapper<T> where T : EventArgs
{
    public Action<EventHandler<T>> Subscribe { get; private set; }
    public Action<EventHandler<T>> Unsubscribe { get; private set; }

    public EventWrapper(Action<EventHandler<T>> subscribe, Action<EventHandler<T>> unsubscribe)
    {
        Subscribe = subscribe;
        Unsubscribe = unsubscribe;
    }
}

In this example, we created a new class called EventWrapper<T> which wraps delegates for += and -= and exposes them with Subscribe and Unsubscribe methods. The delegates will need to be created by the class which created the event.

Upvotes: 0

Jo Ham
Jo Ham

Reputation: 148

Giving an update to this question with an object oriented solution.

Instead of using an Action<EventHandler> that registers the event, you could create an object handling that for you

    public class AEvent
    {
        private readonly A aInstance;
        private AEvent(A instance) {
            aInstance = instance;
        }        

        public void Add(EventHandler eventHandler)
            => a.Event1 += eventHandler;

        public void Remove(EventHandler eventHandler)
            => a.Event1 -= eventHandler;

        public EventHandler Invoke => aInstance.Event1;
    }

Then later on use that object like this:

    static void Main(){
        A a = new A();
        AEvent aEvent = new AEvent(A)
        aEvent.Add(handler);
        a.Invoke();
    }

Upvotes: 0

T.Todua
T.Todua

Reputation: 56351

I pass functions/methods (instead of events) like this:

class A
{
    public void something()
    {
        var myAction = 
            new Action<object, object>((sender, evArgs) => {
                MessageBox.Show("hiii, event happens " + (evArgs as  as System.Timers.ElapsedEventArgs).SignalTime); 
            });
        B.timer(myAction);
    }
}

class B
{
    public static void timer( Action<object, System.Timers.ElapsedEventArgs> anyMethod)
    {
        System.Timers.Timer myTimer = new System.Timers.Timer();
        myTimer.Elapsed += new System.Timers.ElapsedEventHandler(anyMethod);
        myTimer.Interval = 2000;
        myTimer.Start();
    }
}

Upvotes: 0

P Daddy
P Daddy

Reputation: 29527

My original answer was suitable from within the class that defined the event, but you've since updated your question to reflect that you wish to accomplish this from outside the defining class, so I've stricken that.

Only the class that defines an event can refer to the implicit delegate variable that the event uses. From outside that class, you only have access to the add and remove methods, via += and -=. This means that you can't do what you're asking, directly. You can, however, use a functional approach.

class A{
    public event EventHandler Event1;

    public void TriggerEvent1(){
        if(Event1 != null)
            Event1(this, EventArgs.Empty);
    }
}

class B{
    static void HandleEvent(object o, EventArgs e){
        Console.WriteLine("Woo-hoo!");
    }

    static void AttachToEvent(Action<EventHandler> attach){
        attach(HandleEvent);
    }

    static void Main(){
        A a = new A();
        AttachToEvent(handler=>a.Event1 += handler);
        a.TriggerEvent1();
    }
}

Upvotes: 29

Radu Simionescu
Radu Simionescu

Reputation: 4677

Your question suggests that you got some mechanisms wrong: You can't pass events!

You most probably want to pass a function as a parameter, so the calling method will call that other method at some point. In technical terms this is a delegate. I suggest using the already defined Action class. Here's an example snippet:

void MyFunction (string otherArguments, Action onFinished){
    ...
    if (onFinished != null)
        onFinished.Invoke();
}

The nice thing about this is that when calling MyFunction you can declare the Action using the inline syntax:

MyFunction("my other argument", ()=>{
    ///do stuff here, which will be execuded when the action is invoked
});

Upvotes: 0

Tom Smykowski
Tom Smykowski

Reputation: 26089

I did it like this:

public AttachToHandleEvent(Object obj, string EventName)
{
    EventInfo mfi = obj.GetType().GetEvent(EventName);
    MethodInfo mobj = mfi.GetAddMethod();
    mobj.Invoke(obj, new object[] { Item_Click});
}

private void Item_Click(object sender, EventArgs e)
{
    MessageBox.Show("lalala");
}

ToolStripMenuItem tool = new ToolStripMenuItem();
AttachToHandleEvent(tool "Click");

Thank you all for advice. This solution could not be done without your help.

Upvotes: 6

Petar Minchev
Petar Minchev

Reputation: 47363

Just write tool.Click += Item_Click;

Edit: From MSDN "Events can only be invoked from within the class or struct where they (it) are declared". So what you are trying to do is not possible. Could you elaborate more on your needs? Why would you want to pass an event as a parameter?

Upvotes: 2

thelost
thelost

Reputation: 6694

    delegate void doIt(object sender, object data);
    event doIt OnDoIt;

    void add(doIt theDel)
    {
        OnDoIt += theDel;
    }

    void doIt1(object a, object b)
    {
    }

    void doIt2(object a, object b)
    {
    }

    void add()
    {
        add(doIt1);
        add(doIt2);
    }

Upvotes: 1

Jamie Ide
Jamie Ide

Reputation: 49251

It's not possible. You can use a delegate instead of an event if that meets your needs.

Upvotes: 2

Related Questions