46

Im trying to write to an API and I need to call an eventhandler when I get data from a table. Something like this:

    public override bool Run(Company.API api)
    {
        SomeInfo _someInfo = new SomeInfo();

        if (_someInfo.Results == 1)
            return true;
        else
            return false;

        using (MyTable table = new MyTable(api))
        {
            table.WhenData += new EventHandler<DataEventArgs<Record>>(table_WhenData);
            table.WhenDead += new EventHandler<EventArgs>(table_WhenDead);
            table.Start();
        }

    public void table_WhenData(object sender, DataEventArgs<Record> e)
    {
        return true;
    }

The problem that Im having is I dont know how to pass a return value back from table_WhenData to the Run method.

Ive tried many ways (like trying to pass _someInfo to the method) but I just cant seem to get the syntax right.

Any suggestion is greatly appreciated.

Leroy Jenkins
  • 2,680
  • 6
  • 25
  • 33
  • the event handler has to be called from somewhere. this is not shown in your code? That is the only place you can check the return from the handler. – simon Sep 18 '09 at 18:46
  • Thank you everyone. Because this is an API there is much of the code dont have access to or I cant change. I just wanted to verify this before sending it back to the developers. Thank you. – Leroy Jenkins Sep 18 '09 at 19:03

4 Answers4

73

The common pattern here is not to return any data from the event handler, but to add properties to your event argument object so that the consumer of the event can set the properties which the caller can then access. This is very common in UI handling code; you see the Cancel event concept all over the place.

The following is pseudo code, and not compile ready. Its intent is to show the pattern.

public class MyEventArgs : EventArgs
{
   public bool Cancel{get;set;}
}

public bool fireEvent()
{
    MyEventArgs e=new MyEventArgs();

    //Don't forget a null check, assume this is an event
    FireEventHandler(this,e);

    return e.Cancel;
}

public HandleFireEvent(object sender, MyEventArgs e)
{
 e.Cancel=true;
}

Edit

I like how Jon Skeet worded this: make the EventArgs mutuable. That is, the consumer of the event can modify the state of the EventArgs object allowing for the raiser of the event to get to that data.

Henrik
  • 613
  • 4
  • 11
JoshBerke
  • 66,142
  • 25
  • 126
  • 164
  • 2
    In case when there are multiple event handlers (some set it to true and other set this object value to false, for the code sample given above), how the event subscriber code determines? is that like the last subscriber which set wins – bashahul Jun 11 '16 at 07:08
  • 1
    Good question you'd probably want to use a different data structure in this case depending on the business rules you have – JoshBerke Jun 13 '16 at 13:32
  • 1
    @bashahul - yes, what the caller sees is the accumulation of what all the handlers do, so if multiple handlers set a value, it will be the last one. If you expect multiple handlers, then you need to decide what the design intent is, and have all the handlers follow that. For example, in the "Cancel" pattern Josh mentioned, as soon as one handler sets Cancel, other handlers could test for that, and do nothing. In some other situation, you might have each handler append an item to a list of responses, so caller can see them all. – ToolmakerSteve Jun 22 '18 at 00:35
32

I know this is an old post, but just in case anyone comes across it, it is certainly possible to do this. You declare your own delegate that returns a value, then base the event off this new delegate. Here is an example:

In the event declarer / publisher:

// the delegate
public delegate string ReturnStringEventHandler(object sender, EventArgs args);
// the event
public event ReturnStringEventHandler StringReturnEvent;
// raise the event
protected void OnStringReturnEvent(EventArgs e)
    {
        if (StringReturnEvent != null)  // make sure at least one subscriber
              // note the event is returning a string
              string myString = StringReturnEvent(this, e);
    }

In the event subscriber:

// Subscribe to event, probably in class constructor / initializer method
StringReturnEvent += HandleStringReturnEvent;

// Handle event, return data
private string HandleStringReturnEvent(object sender, EventArgs e)
{
    return "a string to return";
}

.NET provides an example of this in the AssemblyResolve event, which uses the ResolveEventHandler delegate to return data, in this case a reference to the desired Assembly. MSDN Article on AssemblyResolve event

I have personally used both the AssemblyResolve event and the custom delegate technique to return data from an event, and they both work as expected on Visual Studio 2010.

The other other Alan
  • 1,868
  • 12
  • 21
  • 6
    If you use the custom delegate approach, what happens if you have multiple handlers of the event returning different values? Which return value is received by the event raiser? – Shavais Dec 05 '13 at 20:27
  • 1
    You mentioned a definite draw-back to this approach. The last event handler to be registered with the event is the one whose value will be returned. http://msdn.microsoft.com/en-us/library/aa691375%28VS.71%29.aspx In the "AssemblyResolve" event I mentioned, this is OK, since its only intended to be used one at a time, but you're right - in general, this would be a poor design choice if multiple return values are required. – The other other Alan Dec 06 '13 at 14:48
  • 3
    What about this approach to get each of the return values: http://stackoverflow.com/a/1237017/1999165 – Eric Roller Sep 12 '15 at 22:13
  • You might get an exception called "Embedded statement cannot be a declaration or labeled statement", just add open and close curly braces surrounding "string myString = StringReturnEvent(this, e);". – SurenSaluka Apr 10 '18 at 15:40
24

The only way you can do it is to make one of the arguments (preferably the "args" rather than the sender) mutable. If it's not already mutable, you've basically got problems - there's just no way of getting the information out.

(Okay, there's one way - you can keep the event argument itself immutable, but make one member of it a method which ends up calling a delegate registered by the code raising the event in the first place. But that's horrible...)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

A simple solution is to use a closure:

public override bool Run() {
    SomeInfo someInfo = ...
    table.WhenData += (obj, args) => {
        someInfo.Return = something
    };
}
Igor ostrovsky
  • 7,282
  • 2
  • 29
  • 28
  • the concern I might have with this solution, is the concurrency issues. One thread calling the event, another reading the someInfo variable before the write has completed. It would need multi-thread protection of some sort. – simon Sep 19 '09 at 00:51