【问题标题】:Create Observable from non-standard event (no EventArgs / EventHandler)从非标准事件创建 Observable(无 EventArgs / EventHandler)
【发布时间】:2018-09-26 09:04:08
【问题描述】:

我想为一个定义如下的事件创建一个 Observable:

public event Func<Exception, Task> Closed;

我目前的代码是这样的:

Observable.FromEvent<Func<Exception, Task>, Unit>(h => hub.Closed += h, h=> hub.Closed -= h); 

编译正常,但是抛出了这个运行时异常:

System.ArgumentException: '无法绑定到目标方法,因为 其签名或安全透明度与 委托类型。'

我觉得我做错了。我不习惯从不遵循 EventArgs 模式的事件创建 observables ????

编辑:仅出于说明目的,这是具有经典事件处理外观的完整代码:

class Program
{
    static async Task Main(string[] args)
    {
        var hub = new HubConnectionBuilder().WithUrl("http://localhost:49791/hubs/status")
            .Build();

        hub.On<Status>("SendAction", status => Console.WriteLine($"Altitude: {status.Altitude:F} m"));
        await hub.StartAsync();

        hub.Closed += HubOnClosed;

        while (true)
        {
        }
    }

    private static Task HubOnClosed(Exception arg)
    {
        Console.WriteLine("The connection to the hub has been closed");
        return Task.CompletedTask;
    }
}

【问题讨论】:

  • 您能否展示一个将您的事件与传统事件处理代码一起使用的示例?这样可以更轻松地将其转换为可观察对象。
  • 感谢@Enigmativity。我正在使用来自第 3 方库(SignalR Core)的 Closed 事件。事件定义在这里:github.com/aspnet/SignalR/blob/… 我正在尝试的代码在这个 repo 中:github.com/SuperJMN/SignalRCoreSample。随意克隆它。这真的很简单。运行服务器和客户端(同时),你会得到异常。
  • @Enigmativity 我刚刚添加了带有经典事件处理的代码。我希望这已经足够了。这让我很头疼 :) 非常感谢!

标签: c# .net events observable system.reactive


【解决方案1】:

您需要转换重载。每次我查到这个东西我都会关闭:

IObservable<TEventArgs> Observable.FromEvent<TDelegate, TEventArgs>(
    Func<Action<TEventArgs>, TDelegate> conversion, 
    Action<TDelegate> addHandler, 
    Action<TDelegate> removeHandler>
)

所以在我们的例子中,TEventArgsExceptionTDelegateFunc&lt;Exception, Task&gt;,所以你需要将Action&lt;Exception&gt; 转换为Func&lt;Exception, Task&gt;&gt;,换句话说:Func&lt;Action&lt;Exception&gt;, Func&lt;Exception, Task&gt;&gt;。我假设转换看起来像这样:a =&gt; e =&gt; {a(e); return Task.CompletedTask; }

System.Reactive 需要此转换函数,因为它需要使用适当的委托订阅事件,并以某种方式挂钩您的代码/RX Plumbing 代码。在这种情况下,a(e) 基本上是 RX 管道,然后将异常传递给稍后在反应管道中处理。

完整代码:

class Program
{
    static async Task Main(string[] args)
    {

        Program.Closed += Program.HubOnClosed;
        Observable.FromEvent<Func<Exception, Task>, Exception>(
            a => e => {a(e); return Task.CompletedTask; }, 
            h => Program.Closed += h, 
            h => Program.Closed -= h
        )
            .Subscribe(e =>
            {
                Console.WriteLine("Rx: The connection to the hub has been closed");
            });

        Program.Closed.Invoke(null);
        Program.Closed.Invoke(null);
    }

    private static Task HubOnClosed(Exception arg)
    {
        Console.WriteLine("The connection to the hub has been closed");
        return Task.CompletedTask;
    }

    public static event Func<Exception, Task> Closed;
}

【讨论】:

    【解决方案2】:

    这样的事情可以解决问题吗?

    class Program
    {
        public event Func<Exception, Task> Closed;
    
        static void Main(string[] args)
        {
            Program p = new Program();
            IObservable<Unit> closedObservable = Observable.Create<Unit>(
                observer =>
                {
                    Func<Exception, Task> handler = ex =>
                    {
                        observer.OnNext(Unit.Default);
                        return Task.CompletedTask;
                    };
    
                    p.Closed += handler;
    
                    return () => p.Closed -= handler;
                });
        }
    }
    

    Observable.Create() 是此类异常情况的有用回退。

    顺便说一句,有一个带有非 void 返回委托的事件是非常奇怪的,因为引发事件的代码只会看到最后一个要运行的处理程序的值 - 除非它在某些非空的情况下引发事件标准方式。但是,因为它是库代码,所以你无法控制!

    【讨论】:

    • 也许这行得通,但我宁愿使用Observable.FromEventObservable.FromEventPattern。我怀疑Create 方法在这里是否合理。希望你能理解。
    • 我知道你来自哪里,但我怀疑代表是 Func&lt;&gt; 而不是 Action&lt;&gt; 的事实是如此不寻常,以至于 Rx 根本不允许这样做。传统上它会被认为是“坏的”,尽管我确信 SignalR 开发人员有充分的理由这样做;-)
    • 糟糕,乍一看,这似乎是一个糟糕的设计决策?另外,如果我理解你的话,问题在于代表的签名是Func&lt;Exception, Task&gt;,而不是Action&lt;&gt;,对吧?
    • 没错。我快速查看了您引用的 SignalR 代码,看起来他们是 await-ing 返回的 Task 进行错误记录,但是,如果我理解正确,他们最终会这样做只有一个事件处理程序。
    • 来自here:“当委托调用的方法的签名包含返回值时,委托返回调用列表中最后一个元素的返回值。”
    【解决方案3】:

    尝试以下方法,不要使用您想要的签名,而是尝试一些东西:

    class Program
    {
            public delegate void ClosedEventHandler(object sender, Func<Exception, Task> e);
            public ClosedEventHandler Closed { get; set; }    
    
            static void Main(string[] args)
            {
                Program hub = new Program();
                hub.Closed = hub.SomethingToDoWhenClosed;    
                Observable
                    .FromEventPattern<ClosedEventHandler, Func<Exception, Task>>(
                        h => hub.Closed += h,
                        h => hub.Closed -= h)
                    .Subscribe(x =>
                    {
                        // this is hit
                    });    
                hub.Closed(hub, e => null);
            }
    
            public void SomethingToDoWhenClosed(object sender, Func<Exception, Task> e)
            {
            }
    }
    

    【讨论】:

    • public delegate void ClosedEventHandler(object sender, Func e); 这不会编译
    • 抱歉,邮件格式不正确。我已经对其进行了编辑,现在代码显示正常。我要试试。
    • 链接失效在哪里返回 main.
    • 我忘了返回一个任务,抱歉,但是添加一个返回 Task.Complete,例如,不会改变任何东西。它不会编译。你也可以等待 Observable.FirstAsync。它也不会编译?
    • 上面显示的代码在 VS2017 上为我运行,并且能够调试到订阅中。我认为一旦你添加了额外的异步代码,这很奇怪。
    猜你喜欢
    • 1970-01-01
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-15
    • 2014-10-16
    相关资源
    最近更新 更多