Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can one use a lambda to create a new EventHandler?

Tags:

c#

wcf

xamarin

It seems like I should have everything that I need here, but the particulars of making it happen are driving me crazy.

I have a static utility method which takes a web service client object, extracts a designated EventInfo from it, and is supposed to add some handlers to that event, essentially callbacks for when the web service invocation completes. The problem is, as any given event essentially has its own handler overload (the WCF generated code provides a unique SomeMethodCompletedEventArgs for each method and corresponding event), I cannot figure out how to make this happen.

I have two handlers I want to attach, and the first one is just a lambda function:

(obj, args) => task.Complete()

So what I'd like to do is just this simple:

eventInfo.AddEventHandler(client, new EventHandler((obj, args) => task.Complete()));

This, however, generates a runtime InvalidCastException, because the eventInfo is expecting an EventHandler<SomeMethodCompletedEventArgs>, not a plain EventHandler. I believe this means that I need to dynamically create the EventHandler delegate using eventInfo.EventHandlerType somehow, but I have not figured out to combine that with the lambda function, or otherwise make a receiver that really does not care what particular flavor of EventArgs is being used.

The only workaround that I've found is to create a generic template argument with which to pass in the particular event arguments type. This enables me to go:

eventInfo.AddEventHandler(client, new EventHandler<E>(...));

Where E is the parameter in question. This is obviously clunky however, and it just seems wrong to have to pass this in when the extracted eventInfo should tell us all we need to know.

It is worth noting that I am using a slightly constrained PCL Framework for Xamarin, which apparently does not include the static Delegate.CreateDelegate() method that I've seen mentioned in related problems. I do have access to Activator, though, which should cover most of the same bases.

like image 217
Metameta Avatar asked Apr 15 '15 16:04

Metameta


People also ask

How do you create async in lambda expression?

Open the Functions page of the Lambda console. Choose a function. Under Function overview, choose Add destination. For Source, choose Asynchronous invocation.

Can Lambda return value C#?

The C# language provides built-in support for tuples. You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple.

What is event Handler Lambda?

The Lambda function handler is the method in your function code that processes events. When your function is invoked, Lambda runs the handler method. When the handler exits or returns a response, it becomes available to handle another event.

What is the use of EventHandler?

In programming, an event handler is a callback routine that operates asynchronously once an event takes place. It dictates the action that follows the event. The programmer writes a code for this action to take place. An event is an action that takes place when a user interacts with a program.


2 Answers

In the example you've provided, you should be able to just remove the explicit delegate constructor:

eventInfo.AddEventHandler(client, (obj, args) => task.Complete());

By letting C# infer the delegate type for you, it should create exactly the correct delegate type needed for the parameter.

If that does not address your specific concern, please provide a good, minimal, complete code example that reliably reproduces the problem, along with a clear, precise explanation of why the above approach doesn't help.


As an aside, it's hard to tell from what little code you posted, but it is unusual to have an explicit AddEventHandler() method. Normally, a class would simply expose an event, and you would use the += syntax to subscribe an event handler.


EDIT:

From your comments, I understand that you are required by the API to comply with a dynamically provided event signature. Personally, I think this kind of design is goofy, but I assume you are stuck with it, presumably due to the design of the Xamarin framework.

Taking the stated goal strictly — that is, given an EventHandler instance, produce a new delegate instance, the type of which matches a run-time-provided Type instance — the following method should work for you:

static Delegate CreateDelegate(Type type, EventHandler handler)
{
    return (Delegate)type.GetConstructor(new [] { typeof(object), typeof(IntPtr) })
        .Invoke(new [] { handler.Target, handler.Method.MethodHandle.GetFunctionPointer() });
}

Example usage:

eventInfo.AddEventHandler(client,
    CreateDelegate(eventInfo.EventHandlerType, (obj, args) => task.Complete());

You could write the above as an extension method to simplify invocation (you didn't say what type client is, so I just made it object for the example):

public static void AddEventHandler(this EventInfo eventInfo, object client, EventHandler handler)
{
        object eventInfoHandler = eventInfo.EventHandlerType
            .GetConstructor(new[] { typeof(object), typeof(IntPtr) })
            .Invoke(new[] { handler.Target, handler.Method.MethodHandle.GetFunctionPointer() });

        eventInfo.AddEventHandler(client, (Delegate)eventInfoHandler);
}

Example usage:

eventInfo.AddEventHandler(client, (obj, args) => task.Complete());

Give the extension method a different name if you are worried that at some point the API's own AddEventHandler() method could change in a way that causes the C# compiler to select its implementation instead of the extension method, or of course if it does so today (the above will work assuming just a single AddEventHandler() method overload with the second parameter as Delegate, but again…lacking a good, minimal, complete code example I cannot guarantee that it will work in your own code; using a unique name would guarantee that it will, at the cost of exposing a bit of the "magic" :) ).


Finally, having identified a working solution, I was then able to search Stack Overflow for similar questions and found this one, of which you could argue your own is a duplicate: Using reflection to specify the type of a delegate (to attach to an event)?

I decided to go ahead and edit my own answer here rather than just proposing to close your question, because the answer to the other question doesn't really provide what I think is as elegant or easy-to-use an example.

like image 75
Peter Duniho Avatar answered Oct 13 '22 00:10

Peter Duniho


It turns out this is not so difficult, but does require some amount of code with a little bit of reflection.

The basic idea is to wrap the handler in a generic class parameterized by the event type

HandlerFor<T> : IDisposable where T : EventArgs. 

This class then has a private member function matching the required event handler signature:

void Handle(object sender, T eventArgs) 

that it will register on construction, unregister on disposal and that will invoke the given Action supplied to it in its constructor whenever the event occurs.

To hide the implementation details, and expose only an IDisposable as a handle for controlled event handler life cycle scope and unregistration, I wrapped this in an EventHandlerRegistry class. This will also allow adding improvements in future if required, such as building a factory delegate instead of repeatedly calling Activator.CreateInstance() to build the HandleFor instances.

It looks like this:

public class EventHandlerRegistry : IDisposable
{
    private ConcurrentDictionary<Type, List<IDisposable>> _registrations;

    public EventHandlerRegistry()
    {
        _registrations = new ConcurrentDictionary<Type, List<IDisposable>>();
    }

    public void RegisterHandlerFor(object target, EventInfo evtInfo, Action eventHandler)
    {
        var evtType = evtInfo.EventHandlerType.GetGenericArguments()[0];
        _registrations.AddOrUpdate(
            evtType,
            (t) => new List<IDisposable>() { ConstructHandler(target, evtType, evtInfo, eventHandler) },
            (t, l) => { l.Add(ConstructHandler(target, evtType, evtInfo, eventHandler)); return l; });
    }

    public IDisposable CreateUnregisteredHandlerFor(object target, EventInfo evtInfo, Action eventHandler)
    {
        var evtType = evtInfo.EventHandlerType.GetGenericArguments()[0];
        return ConstructHandler(target, evtType, evtInfo, eventHandler);
    }

    public void Dispose()
    {
        var regs = Interlocked.Exchange(ref _registrations, null);
        if (regs != null)
        {
            foreach (var reg in regs.SelectMany(r => r.Value))
                reg.Dispose();
        }
    }

    private IDisposable ConstructHandler(object target, Type evtType, EventInfo evtInfo, Action eventHandler)
    {
        var handlerType = typeof(HandlerFor<>).MakeGenericType(evtType);
        return Activator.CreateInstance(handlerType, target, evtInfo, eventHandler) as IDisposable;
    }

    private class HandlerFor<T> : IDisposable where T : EventArgs
    {
        private readonly Action _eventAction;
        private readonly EventInfo _evtInfo;
        private readonly object _target;
        private EventHandler<T> _registeredHandler;

        public HandlerFor(object target, EventInfo evtInfo, Action eventAction)
        {
            _eventAction = eventAction;
            _evtInfo = evtInfo;
            _target = target;
            _registeredHandler = new EventHandler<T>(this.Handle);
            _evtInfo.AddEventHandler(target, _registeredHandler);
        }

        public void Unregister()
        {
            var registered = Interlocked.Exchange(ref _registeredHandler, null);
            if (registered != null)
                // Unregistration is awkward: 
                // doing `RemoveEventHandler(_target, registered);` won't work.
                _evtInfo.RemoveEventHandler(_target, new EventHandler<T>(this.Handle));
        }

        private void Handle(object sender, T EventArgs)
        {
            if (_eventAction != null)
                _eventAction();
        }

        public void Dispose()
        {
            Unregister();
        }
    }
}

It supports clean adding and removing of event handlers in a pretty transparent manner. Note: this does not yet implement IDisposable in the recommended manner. You will have to add finalizers and Dispose(bool isFinalizing) yourself.

This shows an example of its usage:

public class MyArgs1 : EventArgs
{
    public string Value1;
}

public class MyEventSource
{
    public event EventHandler<MyArgs1> Args1Event;

    public EventInfo GetEventInfo()
    {
        return this.GetType().GetEvent("Args1Event");
    }

    public void FireOne()
    {
        if (Args1Event != null)
            Args1Event(this, new MyArgs1() { Value1 = "Bla " });
    }
}

class Program
{
    public static void Main(params string[] args)
    {
        var myEventSource = new MyEventSource();
        using (var handlerRegistry = new EventHandlerRegistry())
        {
            handlerRegistry.RegisterHandlerFor(
                myEventSource,
                myEventSource.GetEventInfo(),
                () => Console.WriteLine("Yo there's some kinda event goin on"));
            handlerRegistry.RegisterHandlerFor(
                myEventSource,
                myEventSource.GetEventInfo(),
                () => Console.WriteLine("Yeah dawg let's check it out"));

            myEventSource.FireOne();
        }
        myEventSource.FireOne();
    }
}

When run, it will give the below output:

output

like image 41
Alex Avatar answered Oct 13 '22 00:10

Alex