EventHandler 与 Rx Subject

EventHandler vs Rx Subject

我正在使用 System.Reactive,但我不知道该选择哪一个:EventHandlers 或 Subjects。它们有什么区别?

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

下面是一段代码示例,它更好地说明了使用主题和事件来创建可观察对象。

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

现在我可以执行了:

    var foo = new Foo();

    foo.Bangs.Subscribe(_ => Console.WriteLine("Bang!"));
    foo.Booms.Subscribe(_ => Console.WriteLine("Boom!"));

    foo.OnExplode();

我在控制台上得到的结果是这样的:

Bang!
Boom!

代码很好地完成了工作。

现在,这两种方法的问题是 Foo class 的恶意编码者。他们可以添加这个方法:

public void Nefarious()
{
    _boom.OnCompleted();
    _bang = null;
}

这有效地终止了代码。

现在我可以运行这个:

var foo = new Foo();

foo.Bangs.Subscribe(_ => Console.WriteLine("Bang!"));
foo.Booms.Subscribe(_ => Console.WriteLine("Boom!"));

foo.OnExplode();
foo.Nefarious();
foo.OnExplode();

我仍然只会看到一次输出。

两者都可以损坏。

但总的来说,我发现很少有人将他们的事件委托设置为 null

主题结束或出错的可能性要大得多,从而使代码表单停止工作。