简体   繁体   中英

EventHandler vs Rx Subject

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

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.

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