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

Met*_*eta 7 c# wcf xamarin

看起来像我应该有我需要这里的一切,但是使它的细节恰巧被我逼疯了.

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

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

(obj, args) => task.Complete()
Run Code Online (Sandbox Code Playgroud)

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

eventInfo.AddEventHandler(client, new EventHandler((obj, args) => task.Complete()));
Run Code Online (Sandbox Code Playgroud)

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

我发现的唯一解决方法是创建一个通用模板参数,用于传递特定的事件参数类型.这使我能够:

eventInfo.AddEventHandler(client, new EventHandler<E>(...));
Run Code Online (Sandbox Code Playgroud)

E参数在哪里有问题.然而,这显然是笨拙的,当提取的信息eventInfo告诉我们所有我们需要知道的时候,必须通过它似乎是错误的.

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

Pet*_*iho 6

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

eventInfo.AddEventHandler(client, (obj, args) => task.Complete());
Run Code Online (Sandbox Code Playgroud)

通过让 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() });
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

eventInfo.AddEventHandler(client,
    CreateDelegate(eventInfo.EventHandlerType, (obj, args) => task.Complete());
Run Code Online (Sandbox Code Playgroud)

你可以把上面写成一个扩展方法来简化调用(你没有说是什么类型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);
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

eventInfo.AddEventHandler(client, (obj, args) => task.Complete());
Run Code Online (Sandbox Code Playgroud)

如果您担心 API 自己的AddEventHandler()方法在某些时候可能会以某种方式更改,从而导致 C# 编译器选择其实现而不是扩展方法,或者当然如果今天这样做(上述假设只有一个AddEventHandler()方法重载,第二个参数为 as Delegate,但同样......缺乏一个好的,最小的完整的代码示例我不能保证它会在你自己的代码中工作;使用唯一的名称将保证它会,在暴露一些“魔法”的成本:))。


最后,确定了一个可行的解决方案后,我就可以在 Stack Overflow 上搜索类似的问题,并找到了这个问题,您可以认为自己的问题是重复的:使用反射指定委托的类型(附加到事件)?

我决定继续在这里编辑我自己的答案,而不仅仅是建议结束您的问题,因为另一个问题的答案并没有真正提供我认为优雅或易于使用的示例。


Ale*_*lex 1

事实证明这并不是那么困难,但确实需要一定量的代码和一点点反射。

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

HandlerFor<T> : IDisposable where T : EventArgs. 
Run Code Online (Sandbox Code Playgroud)

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

void Handle(object sender, T eventArgs) 
Run Code Online (Sandbox Code Playgroud)

它将在构造时注册,在处置时取消注册,并且Action每当事件发生时都会调用其构造函数中提供给它的给定值。

为了隐藏实现细节,并仅公开 anIDisposable作为受控事件处理程序生命周期范围和取消注册的句柄,我将其包装在一个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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它支持以相当透明的方式干净地添加和删除事件处理程序。注意:这尚未以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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

输出