3

For a simple example, if I had some sort of button UI class, could I write a function that takes an expression that points to its Click event handler:

SomeMethod<SomeButtonClass>(button => button.Click);

I'm trying to eliminate some magic strings currently being used for a system to make events awaitable. The code in question is derived from a blog post by Frank Krueger (a worthwhile read, if you want some background).

public static Task<TEventArgs> GetEventAsync<TEventArgs>(this object eventSource, string eventName) where TEventArgs : EventArgs {
    //...
    Type type = eventSource.GetType();
    EventInfo ev = type.GetEvent(eventName);
    //...
}

While the specifics inside probably aren't important, the full method allows you to use an Event triggering as the completion source for a Task, making it easier to manage with await. For some class that raises an event, you can tie into a Task based on that event with a simple call.

Task<EventArgs> eventTask = someEventCausingObject.GetEventAsync<EventArgs>("SomeEventHandler");
// traditionally used as someEventCausingObject.SomeEventHandler += ...;
await eventTask;
// Proceed back here when SomeEventHandler event is raised.

I have been using this happily for a couple projects, but it has its drawbacks, one of the biggest being the use of hard-coded event name strings. This makes event name changes turn into runtime exceptions, and determining usage of the event is difficult.

I started trying to make a version that would allow the EventHandler to be passed in as part of an Expression with the goal of something like this:

await someEventCausingObject.GetEventAsync<EventCausingClass, EventArgs>(x => x.SomeEventHandler);

...with the corresponding method signature...

public static Task<TEventArgs> GetEventAsync<TSource, TEventArgs>(this TSource eventSource, Expression<Func<TSource, EventHandler>> eventHandlerExpression) where TEventArgs : EventArgs {
    //...
}

Unfortunately, the lambda expression in the calling code causes a compile error:

Error CS0070: The event `SomeEventHandler' can only appear on the left hand side of += or -= when used outside of the type `EventCausingClass'.

This makes some sense given how event handlers are typically used, but I was hoping to find a better solution going forward than the pre-specified string name. It seems searches for combinations of "expression" and "eventhandler" all tend to be polluted with people describing lambda expressions for beginning += event handler assignment. I'm hoping I am missing something obvious here.

Jonathan Nixon
  • 4,940
  • 5
  • 39
  • 53
patridge
  • 26,385
  • 18
  • 89
  • 135
  • possible duplicate of [General purpose FromEvent method](http://stackoverflow.com/questions/12865848/general-purpose-fromevent-method) – Servy Feb 20 '14 at 19:50
  • The short answer is no. You're not missing something, it's just not possible to use that syntax. – Servy Feb 20 '14 at 19:52
  • Note also that Button.Click can contain multiple event handlers attached to it which would be counter to your logic here of extracting info for one particular event handler. – Dmitriy Khaykin Feb 20 '14 at 19:55
  • @DavidKhaykin What he wants to do is get a reference to the event so that the can add a handler, not look at the handlers that are there. – Servy Feb 20 '14 at 19:56
  • Thanks for that. I was definitely missing that question in my hunt. It is an interesting read, for sure, seeing their approach to async-ifying all events on a class, although I'm in a Xamarin environment and may not be able to duplicate their efforts. – patridge Feb 20 '14 at 19:57
  • @patridge If you specifically limit yourself to *only* events with a signature of `EventHandler` it does simplify the code a bit over what's there, but then you can't use it with events that aren't of type `EventHandler` and even with that constraint you still can't get the syntax as good as you have requested. In particular I'm referring to the general approach of svick's answer. – Servy Feb 20 '14 at 19:58
  • @Servy I finally got through most of that question. It's a really good read. They definitely found the same limitation on that question, though I probably wouldn't call it a duplicate since the two questions have completely different goals. The answer over there wouldn't answer this question at all. – patridge Feb 20 '14 at 20:04
  • The questions are strictly the same thing, but that question *does* answer this question. It specifically states that it's not possible, at least without additional language support, and it has answers containing your best bet for workarounds to the problem that give you as much as you can get given the language limitations. The fact that it's not strictly the same question but still answers this question makes it a suitable duplicate. – Servy Feb 20 '14 at 20:10

1 Answers1

1

No, it is not possible to target an event. Basically event is not a real type member, but just C# syntax which produces add_EventName and remove_EventName methods pair.

You could try refer to these internal methods name, but it's not possible in C# - http://msdn.microsoft.com/en-us/library/z47a7kdw.aspx

There are many similar questions in SO, with the same answer NO - like this one from Jon Skeet https://stackoverflow.com/a/4756021/2170171

If you're real crazy, you can try something like

private static void Subscribe(Action addHandler)
{
    var IL = addHandler.Method.GetMethodBody().GetILAsByteArray();

    // Magic here, in which we understand ClassName and EventName
    ???
}

with usage like

Subscribe(() => new Button().Click += null);

You could try using Cecil http://www.mono-project.com/Cecil for analyzing IL, or implement your own logic as it should not be too hard for predictable line of code.

I don't think that it is good solution though, as it just replaces one headache (proper event naming) with another one (proper Subscribe calling). Though, it will help with rename stuff.

Community
  • 1
  • 1
Lanorkin
  • 7,310
  • 2
  • 42
  • 60