如何使用lambda创建一个新的EventHandler?

似乎我应该拥有我需要的一切,但是让它发生的细节让我发疯。

我有一个静态实用程序方法,它接受一个Web服务客户端对象,从中提取一个指定的EventInfo,并且应该为该事件添加一些处理程序,本质上是在Web服务调用完成时进行回调。 问题是,因为任何给定的事件本质上都有自己的处理程序重载(WCF生成的代码为每个方法和相应的事件提供了唯一的SomeMethodCompletedEventArgs ),所以我无法弄清楚如何实现这种情况。

我有两个处理程序要附加,第一个只是一个lambda函数:

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

所以我想要做的就是这么简单:

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

然而,这会生成一个运行时InvalidCastException,因为eventInfo期待一个EventHandler<SomeMethodCompletedEventArgs> ,而不是一个普通的EventHandler 。 我相信这意味着我需要以某种方式使用eventInfo.EventHandlerType动态创建EventHandler委托,但是我还没有想到将它与lambda函数结合起来,或者让接收器真的不在意EventArgs特定风格用过的。

我发现的唯一解决方法是创建一个通用模板参数,并在其中传入特定的事件参数类型。 这使我可以去:

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

E是所讨论的参数。 然而,这显然很笨重,当提取的eventInfo应该告诉我们所有我们需要知道的时候,必须通过这个过程似乎是错误的。

值得注意的是,我为Xamarin使用了一个稍微受限的PCL框架,它显然不包括我在相关问题中提到的静态Delegate.CreateDelegate()方法。 我确实可以访问Activator ,但它应该涵盖大部分相同的基础。


事实证明,这并不难,但需要一定量的代码和一点反思。

基本的想法是将处理程序包装在由事件类型参数化的泛型类中

HandlerFor<T> : IDisposable where T : EventArgs. 

这个类然后有一个私人成员函数匹配所需的事件处理程序签名:

void Handle(object sender, T eventArgs) 

它将在施工中注册,取消注册处理,并在事件发生时调用在其构造器中提供给它的给定Action

为了隐藏实现细节,并仅将IDisposable作为受控事件处理程序生命周期范围和注销的句柄公开,我将其包装在EventHandlerRegistry类中。 如果需要,这也将允许在未来增加改进,例如构建工厂委托而不是重复调用Activator.CreateInstance()来构建HandleFor实例。

它看起来像这样:

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();
        }
    }
}

它支持以相当透明的方式干净地添加和删除事件处理程序。 注意:这还没有以推荐的方式实现IDisposable 。 你将不得不自己添加终结器和Dispose(bool isFinalizing)

这显示了它的用法的一个例子:

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();
    }
}

运行时,它会给出以下输出:


在你提供的例子中,你应该能够删除显式的委托构造函数:

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

通过让C#为您推断委托类型,它应该为参数创建完全正确的委托类型。

如果这不能解决您的具体问题,请提供一个很好,最简单,完整的代码示例,以便可靠地重现问题,并清楚准确地解释为什么上述方法无效。


顺便说一下,很难从发布的代码中分辨出什么,但是有一个明确的AddEventHandler()方法是不寻常的。 通常,一个类只会暴露一个事件,并且你会使用+=语法来订阅事件处理程序。


编辑:

从您的意见中,我了解到API要求您遵守动态提供的事件签名。 就我个人而言,我认为这种设计太愚蠢了,但我认为你被卡住了,大概是由于Xamarin框架的设计。

严格遵守规定的目标 - 即给定一个EventHandler实例,生成一个新的委托实例,其类型与运行时提供的Type实例相匹配 - 以下方法应该适用于您:

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

用法示例:

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

你可以把上面的代码写成扩展方法来简化调用(你没有说client是什么类型的,所以我只是把它作为object ):

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);
}

用法示例:

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

如果您担心在某些时候API的自己的AddEventHandler()方法可能导致C#编译器选择其实现而不是扩展方法,或者当然如果它今天这样做(上面的代码只用一个AddEventHandler()方法重载第二个参数作为Delegate ,但是又一次......缺少一个好的,最小的,完整的代码示例我不能保证它能在你自己的代码中工作;使用一个唯一的名字会保证它会以暴露一点“魔力”为代价:))。


最后,在确定了一个工作解决方案之后,我可以在堆栈溢出中搜索类似的问题,并找到了这个问题,然后您可以说您自己的问题是重复的:使用反射指定代理的类型(以附加到事件)?

我决定继续在这里编辑我自己的答案,而不是只提出关闭你的问题,因为对另一个问题的回答并不真正提供我认为是优雅或易于使用的例子。

链接地址: http://www.djcxy.com/p/25621.html

上一篇: How can one use a lambda to create a new EventHandler?

下一篇: MEANJS boilerplate : where to include custom javascript files?