简体   繁体   English

Rx.NET“门”运算符

[英]Rx.NET “gate” operator

[Note: I am using 3.1 if that matters. [注意:如果有问题,我正在使用3.1。 Also, I've asked this on codereview but no responses so far.] 另外,我已经在codereview上询问了此问题,但到目前为止没有任何回复。]

I need an operator to allow a stream of booleans to act as a gate for another stream (let values pass when the gate stream is true, drop them when it's false). 我需要一个运算符,以允许布尔流充当另一个流的门(当Gate流为true时,让值通过,否则为false)。 I would normally use Switch for this, but if the source stream is cold it will keep recreating it, which I don't want. 我通常会为此使用Switch,但是如果源流很冷,它将继续重新创建它,这是我不想要的。

I also want to clean up after myself, so that the result completes if either of the source or the gate complete. 我也想自己清理一下,以便在源或门都完成的情况下完成结果。

public static IObservable<T> When<T>(this IObservable<T> source, IObservable<bool> gate)
{
    var s = source.Publish().RefCount();
    var g = gate.Publish().RefCount();

    var sourceCompleted = s.TakeLast(1).DefaultIfEmpty().Select(_ => Unit.Default);
    var gateCompleted = g.TakeLast(1).DefaultIfEmpty().Select(_ => Unit.Default);

    var anyCompleted = Observable.Amb(sourceCompleted, gateCompleted);

    var flag = false;
    g.TakeUntil(anyCompleted).Subscribe(value => flag = value);

    return s.Where(_ => flag).TakeUntil(anyCompleted);
}

Besides the overall verbosity, I dislike that I subscribe to the gate even if the result is never subscribed to (in which case this operator should be a no-op). 除了总体上的冗长之外,我不喜欢我订阅门,即使结果从不订阅(在这种情况下,此运算符应为no-op)。 Is there a way to get rid of that subscribe? 有没有摆脱该订阅的方法?

I have also tried this implementation, but it's even worse when it comes to cleaning up after itself: 我也尝试过这种实现,但是在进行清理时更糟:

return Observable.Create<T>(
    o =>
    {
        var flag = false;
        gate.Subscribe(value => flag = value);

        return source.Subscribe(
            value =>
            {
                if (flag) o.OnNext(value);
            });
    });

These are the tests I'm using to check the implementation: 这些是我用来检查实现的测试:

[TestMethod]
public void TestMethod1()
{
    var output = new List<int>();

    var source = new Subject<int>();
    var gate = new Subject<bool>();

    var result = source.When(gate);
    result.Subscribe(output.Add, () => output.Add(-1));

    // the gate starts with false, so the source events are ignored
    source.OnNext(1);
    source.OnNext(2);
    source.OnNext(3);
    CollectionAssert.AreEqual(new int[0], output);

    // setting the gate to true will let the source events pass
    gate.OnNext(true);
    source.OnNext(4);
    CollectionAssert.AreEqual(new[] { 4 }, output);
    source.OnNext(5);
    CollectionAssert.AreEqual(new[] { 4, 5 }, output);

    // setting the gate to false stops source events from propagating again
    gate.OnNext(false);
    source.OnNext(6);
    source.OnNext(7);
    CollectionAssert.AreEqual(new[] { 4, 5 }, output);

    // completing the source also completes the result
    source.OnCompleted();
    CollectionAssert.AreEqual(new[] { 4, 5, -1 }, output);
}

[TestMethod]
public void TestMethod2()
{
    // completing the gate also completes the result
    var output = new List<int>();

    var source = new Subject<int>();
    var gate = new Subject<bool>();

    var result = source.When(gate);
    result.Subscribe(output.Add, () => output.Add(-1));

    gate.OnCompleted();
    CollectionAssert.AreEqual(new[] { -1 }, output);
}

Update : This terminates when gate terminates as well. 更新 :这也将在门也终止时终止。 I missed TestMethod2 in the copy/paste: 我在复制/粘贴中错过了TestMethod2

    return gate.Publish(_gate => source
        .WithLatestFrom(_gate.StartWith(false), (value, b) => (value, b))
        .Where(t => t.b)
        .Select(t => t.value)
        .TakeUntil(_gate.IgnoreElements().Materialize()
    ));

This passes your tests TestMethod1 , it doesn't terminate when the gate observable does. 这将通过 您的测试 TestMethod1 ,但当可观察到的门不会终止。

public static IObservable<T> When<T>(this IObservable<T> source, IObservable<bool> gate)
{
    return source
        .WithLatestFrom(gate.StartWith(false), (value, b) => (value, b))
        .Where(t => t.b)
        .Select(t => t.value);
}

This works: 这有效:

public static IObservable<T> When<T>(this IObservable<T> source, IObservable<bool> gate)
{
    return
        source.Publish(ss => gate.Publish(gs =>
            gs
                .Select(g => g ? ss : ss.IgnoreElements())
                .Switch()
                .TakeUntil(Observable.Amb(
                    ss.Select(s => true).Materialize().LastAsync(),
                    gs.Materialize().LastAsync()))));
}

This passes both tests. 这通过了两个测试。

You were on the right track with Observable.Create . 使用Observable.Create使您Observable.Create正确的轨道上。 You should call the onError and onCompleted from both subscriptions on the observable to properly complete or error it when needed. 您应该在可观察对象上的两个订阅中调用onError和onCompleted,以在需要时正确完成或出错。 Also by returning both the IDisposable s within the Create delegate you make sure both subscriptions are properly cleaned up if you intend to dispose the When subscription before either source or gate completes. 同样,通过返回Create委托中的两个IDisposable ,可以确保如果打算在sourcegate完成之前处置When订阅,则可以正确清理两个订阅。

    public static IObservable<T> When<T>(this IObservable<T> source, IObservable<bool> gate)
    {
        return Observable.Create<T>(
            o =>
            {
                var flag = false;
                var gs = gate.Subscribe(
                    value => flag = value,
                    e => o.OnError(e),
                    () => o.OnCompleted());

                var ss = source.Subscribe(
                    value =>
                    {
                        if (flag) o.OnNext(value);
                    },
                    e => o.OnError(e), 
                    () => o.OnCompleted());

                return new CompositeDisposable(gs, ss);
            });
    }

A shorter, but much harder to read version using only Rx operators. 仅使用Rx运算符的简短版本,但更难阅读。 For cold observables it probably needs a publish/refcount for the source. 对于冷的可观察物,可能需要为源发布/引用计数。

    public static IObservable<T> When<T>(this IObservable<T> source, IObservable<bool> gate)
    {
        return gate
            .Select(g => g ? source : source.IgnoreElements())
            .Switch()
            .TakeUntil(source.Materialize()
                             .Where(s => s.Kind == NotificationKind.OnCompleted));
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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