简体   繁体   中英

Create Observable from non-standard event (no EventArgs / EventHandler)

I would like to create an Observable for an event defined as follows:

public event Func<Exception, Task> Closed;

The current code I have is this:

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

It compiles OK, but it throws this runtime exception:

System.ArgumentException: 'Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.'

I feel that I'm doing it wrong. I'm not used to create observables from events that don't follow the EventArgs pattern 😔

EDIT: Just for clarification purposes, this is the complete code with how the classic event handling would look:

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

You need the conversion overload. I shutter every time I look this thing up:

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

So in our case, TEventArgs is Exception , and TDelegate is Func<Exception, Task> , so you need to convert Action<Exception> to Func<Exception, Task>> , in other words: Func<Action<Exception>, Func<Exception, Task>> . I'm assuming that conversion looks like this: a => e => {a(e); return Task.CompletedTask; } a => e => {a(e); return Task.CompletedTask; } a => e => {a(e); return Task.CompletedTask; } .

System.Reactive needs this conversion function because it needs to subscribe to the event with a proper delegate, and somehow hook in your code/RX Plumbing code. In this case, a(e) is basically RX plumbing which then passes on the Exception to be handled later in the reactive pipeline.

Full code:

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

Does something like this do the trick?

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() is a useful fallback for unusual cases like this.

As an aside, it's very strange to have an event with a non-void returning delegate, since the code that raises the event would only see the value of the last handler to run - unless it raises the event in some non-standard way. But, since it's library code, that's out of your hands!

Try the following, not using the signature you want but something to try:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM