I'm using System.Reactive
and I don't know which one to choose: EventHandlers or Subjects. What's the difference between them?
var client = WebSocketClient.Create(uri);
// Subject
client.OnConnected
.Subscribe(_ => { Log.Information($"Socket {client.Id} connected"); })
.DisposeWith(disposable);
// EventHandler
Observable
.FromEventPattern(h => client.Connected = h, h => client.Connected -= h)
.Select(_ => Unit.Default)
.Subscribe(_ => { Log.Information($"Socket {client.Id} connected"); })
.DisposeWith(disposable);
public class WebSocketClient : IWebSocketClient
{
// Subject
private readonly ISubject<Unit> _connectedSubject = new Subject<Unit>();
public IObservable<Unit> OnConnected => _connectedSubject.AsObservable();
// EventHandler
private EventHandler? _connected;
public event EventHandler Connected
{
add => _connected = value;
remove => _connected -= value;
}
// Logic
async Task IWebSocketClient.ConnectAsync(CancellationToken cancellationToken)
{
...
await _webSocket.ConnectAsync(_uri, cancellationToken).ConfigureAwait(false);
_connected?.Invoke(this, EventArgs.Empty);
_connectedSubject.OnNext();
...
}
private void Dispose()
{
_connectedSubject.OnCompleted();
}
}
CodePudding user response:
Here's an example bit of code that better illustrates the use of a subject versus an event to create an observable.
public class Foo
{
private event EventHandler<Unit> _bang;
public IObservable<Unit> Bangs =>
Observable
.FromEventPattern<Unit>(h => _bang = h, h => _bang -= h)
.Select(x => x.EventArgs);
private Subject<Unit> _boom = new Subject<Unit>();
public IObservable<Unit> Booms =>
_boom.AsObservable();
public void OnExplode()
{
_bang?.Invoke(this, Unit.Default);
_boom.OnNext(Unit.Default);
}
}
Now I can execute it:
var foo = new Foo();
foo.Bangs.Subscribe(_ => Console.WriteLine("Bang!"));
foo.Booms.Subscribe(_ => Console.WriteLine("Boom!"));
foo.OnExplode();
The result I get on the console is this:
Bang!
Boom!
The code does the job well.
Now, the concern with both approaches is a nefarious coder of the Foo
class. They could add this method:
public void Nefarious()
{
_boom.OnCompleted();
_bang = null;
}
That effectively kills the code.
Now I could run this:
var foo = new Foo();
foo.Bangs.Subscribe(_ => Console.WriteLine("Bang!"));
foo.Booms.Subscribe(_ => Console.WriteLine("Boom!"));
foo.OnExplode();
foo.Nefarious();
foo.OnExplode();
And I'll still only see the output once.
Both can be corrupted.
In general, though, I find that few people set their event delegates to null
.
It's far more likely that a subject will be ended or error, thus stopping the code form working.