How can one use a lambda to create a new EventHandler?
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.
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:
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.
链接地址: http://www.djcxy.com/p/25622.html上一篇: 内存泄漏终结器错误