Reputation: 14002
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;
}
}
Upvotes: 3
Views: 984
Reputation: 14370
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; }
.
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;
}
Upvotes: 9
Reputation: 6026
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!
Upvotes: 2
Reputation: 59
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)
{
}
}
Upvotes: -2