C# 如果第一个IObservable为空,则切换到其他IObservable

C# 如果第一个IObservable为空,则切换到其他IObservable,c#,.net,system.reactive,C#,.net,System.reactive,我正在编写一个函数,用于检索有关主题的新闻,并通过IObservable返回值反馈该新闻 然而,我有几个新闻来源。我不想使用Merge将这些源合并为一个。相反,我想做的是按优先级排序-- 调用my函数时,将查询第一个新闻源(这将生成表示该源的IObservable) 如果该新闻源的IObservable完成而未返回任何结果,则会查询下一个新闻源 如果第二个消息源完成而没有返回结果,则查询最终的新闻源 这整个行为被包装成一个可观察的我可以返回给用户 这种行为是使用内置Rx扩展方法可以实现的,还是需

我正在编写一个函数,用于检索有关主题的新闻,并通过IObservable返回值反馈该新闻

然而,我有几个新闻来源。我不想使用
Merge
将这些源合并为一个。相反,我想做的是按优先级排序--

  • 调用my函数时,将查询第一个新闻源(这将生成表示该源的IObservable)
  • 如果该新闻源的IObservable完成而未返回任何结果,则会查询下一个新闻源
  • 如果第二个消息源完成而没有返回结果,则查询最终的新闻源
  • 这整个行为被包装成一个可观察的我可以返回给用户
  • 这种行为是使用内置Rx扩展方法可以实现的,还是需要实现一个自定义类来处理?我该怎么做呢?

    从原始海报编辑: 我同意这个答案,但把它变成了一种扩展方法--

    ///返回第一个序列的元素,如果第一个序列为空,则返回第二个序列中的值。
    ///第一个序列。
    ///第二个序列。
    ///序列中元素的类型。
    ///顺序。
    公共静态IObservable DefaultIfEmpty(此IObservable第一个,IObservable第二个)
    {
    var signal=new AsyncSubject();
    var source1=first.Do(item=>{signal.OnNext(Unit.Default);signal.OnCompleted();});
    var source2=第二个.TakeUntil(信号);
    返回source1.Concat(source2);//如果source2是冷的,则在source1完成之前不会调用它
    }
    
    原来的答案是: 这可能会奏效

    var signal1 = new AsyncSubject<Unit>();
    var signal2 = new AsyncSubject<Unit>();
    var source1 = a.Do(item => { signal1.onNext(Unit.Default); signal1.onCompleted(); });
    var source2 = b.Do(item => { signal2.onNext(Unit.Default); signal2.onCompleted(); })).TakeUntil(signal1);
    var source3 = c.TakeUntil(signal2.Merge(signal1));
    
    return Observable.Concat(source1, source2, source3);
    
    var signal1=new AsyncSubject();
    var signal2=新的AsyncSubject();
    var source1=a.Do(item=>{signal1.onNext(Unit.Default);signal1.onCompleted();});
    var source2=b.Do(item=>{signal2.onNext(Unit.Default);signal2.onCompleted();})).TakeUntil(signal1);
    var source3=c.TakeUntil(signal2.Merge(signal1));
    返回Observable.Concat(source1、source2、source3);
    
    编辑:哎呀,第二个信号源需要一个单独的信号,第三个信号源不需要任何信号。 编辑2:哎呀……类型。我习惯了RxJs:)

    另外,RX-y的方式也较少,这可能是因为打字少了一点:

    var gotResult = false;
    var source1 = a();
    var source2 = Observable.Defer(() => return gotResult ? Observable.Empty<T>() : b());
    var source3 = Observable.Defer(() => return gotResult ? Observable.Empty<T>() : c());
    return Observable.Concat(source1, source2, source3).Do(_ => gotResult = true;);
    
    var gotResult=false;
    var source1=a();
    var source2=Observable.Defer(()=>return-gotResult?Observable.Empty():b());
    var source3=Observable.Defer(()=>return-gotResult?Observable.Empty():c());
    返回Observable.Concat(source1,source2,source3).Do(=>gotreult=true;);
    
    听起来您可以使用普通的
    Amb
    查询

    编辑:根据评论,
    Amb
    不会这样做-对其进行重击:

    public static IObservable<T> SwitchIfEmpty<T>(
         this IObservable<T> first, 
         Func<IObservable<T>> second)
    {
        return first.IsEmpty().FirstOrDefault() ? second() : first;
    }
    
    编辑:

    我想你也可以把它概括为:

    public static IObservable<T> SwitchIf<T>(
        this IObservable<T> first, 
        Func<IObservable<T>, IObservable<bool>> predicate, 
        Func<IObservable<T>> second)
    {
        return predicate(first).FirstOrDefault() 
            ? second() 
            : first;
    }
    
    公共静态IObservable开关IF(
    首先,这是可以观察到的,
    Func谓词,
    (第二)
    {
    返回谓词(first).FirstOrDefault()
    ?第二()
    :第一;
    }
    
    另一种方法-与其他方法相比差异相当大,因此我将提出一个新的答案:

    下面是各种有趣的调试行:

    public static IObservable<T> FirstWithValues<T>(this IEnumerable<IObservable<T>> sources)
    {
        return Observable.Create<T>(obs =>
        {
            // these are neat - if you set it's .Disposable field, and it already
            // had one in there, it'll auto-dispose it
            SerialDisposable disp = new SerialDisposable();
            // this will trigger our exit condition
            bool hadValues = false;
            // start on the first source (assumed to be in order of importance)
            var sourceWalker = sources.GetEnumerator();
            sourceWalker.MoveNext();
    
            IObserver<T> checker = null;
            checker = Observer.Create<T>(v => 
                {
                    // Hey, we got a value - pass to the "real" observer and note we 
                    // got values on the current source
                    Console.WriteLine("Got value on source:" + v.ToString());
                    hadValues = true;
                    obs.OnNext(v);
                },
                ex => {
                    // pass any errors immediately back to the real observer
                    Console.WriteLine("Error on source, passing to observer");
                    obs.OnError(ex);
                },
                () => {
                    // A source completed; if it generated any values, we're done;                    
                    if(hadValues)
                    {
                        Console.WriteLine("Source completed, had values, so ending");
                        obs.OnCompleted();
                    }
                    // Otherwise, we need to check the next source in line...
                    else
                    {
                        Console.WriteLine("Source completed, no values, so moving to next source");
                        sourceWalker.MoveNext();
                        disp.Disposable = sourceWalker.Current.Subscribe(checker);
                    }
                });
            // kick it off by subscribing our..."walker?" to the first source
            disp.Disposable = sourceWalker.Current.Subscribe(checker);
            return disp.Disposable;
        });
    }
    
    输出:

    Source A invoked
    Got value on source:Article from A
    Article from A
    Source completed, had values, so ending
    
    Source A invoked
    Source completed, no values, so moving to next source
    Source B invoked
    Got value on source:Article from B
    Article from B
    Source completed, had values, so ending
    
    Source A invoked
    Source completed, no values, so moving to next source
    Source B invoked
    Source completed, no values, so moving to next source
    Source C invoked
    Got value on source:Article from C
    Article from C
    Source completed, had values, so ending
    

    在我看来,接受的答案是不可取的,因为它使用了
    Subject
    Do
    ,并且在第一个序列不是空的情况下仍然订阅第二个序列。如果第二个可观察对象调用任何非平凡的东西,那么后者可能是一个大问题。我提出了以下解决方案:

    public static IObservable<T> SwitchIfEmpty<T>(this IObservable<T> @this, IObservable<T> switchTo)
    {
        if (@this == null) throw new ArgumentNullException(nameof(@this));
        if (switchTo == null) throw new ArgumentNullException(nameof(switchTo));
        return Observable.Create<T>(obs =>
        {
            var source = @this.Replay(1);
            var switched = source.Any().SelectMany(any => any ? Observable.Empty<T>() : switchTo);
            return new CompositeDisposable(source.Concat(switched).Subscribe(obs), source.Connect());
        });
    }
    
    public static IObservable SwitchIfEmpty(this IObservable@this,IObservable switchTo)
    {
    如果(@this==null)抛出新的ArgumentNullException(nameof(@this));
    如果(switchTo==null)抛出新的ArgumentNullException(nameof(switchTo));
    返回可观察的。创建(obs=>
    {
    var source=@this.Replay(1);
    var switched=source.Any().SelectMany(Any=>Any?Observable.Empty():switcheto);
    返回新的CompositeDisposable(source.Concat(switched.Subscribe(obs),source.Connect());
    });
    }
    
    名称
    SwitchIfEmpty
    与。是关于将一些RxJava操作符合并到RxNET中的持续讨论


    我确信一个定制的
    IObservable
    实现将比我的实现更高效。您可以找到一个由ReactiveX成员编写的。它也可以在上找到。

    这里是JerKimball运算符的非阻塞版本

    /// <summary>Returns the elements of the first sequence, or the elements of the
    /// second sequence if the first sequence is empty.</summary>
    public static IObservable<T> SwitchIfEmpty<T>(this IObservable<T> first,
        IObservable<T> second)
    {
        return Observable.Defer(() =>
        {
            bool isEmpty = true;
            return first
                .Do(_ => isEmpty = false)
                .Concat(Observable.If(() => isEmpty, second));
        });
    }
    
    ///返回第一个序列的元素或
    ///如果第一个序列为空,则为第二个序列。
    公共静态IObservable SwitchIfEmpty(此IObservable优先,
    IObservable(秒)
    {
    返回可观察的延迟(()=>
    {
    bool isEmpty=true;
    先返回
    .Do(=>isEmpty=false)
    .Concat(可观察。如果(()=>为空,则为秒));
    });
    }
    
    这是同一个操作符的一个版本,它接受多个序列,并返回第一个非空序列的元素:

    /// <summary>Returns the elements of the first non-empty sequence.</summary>
    public static IObservable<T> SwitchIfEmpty<T>(params IObservable<T>[] sequences)
    {
        return Observable.Defer(() =>
        {
            bool isEmpty = true;
            return sequences
                .Select(s => s.Do(_ => isEmpty = false))
                .Select(s => Observable.If(() => isEmpty, s))
                .Concat();
        });
    }
    
    ///返回第一个非空序列的元素。
    公共静态IObservable SwitchIfEmpty(参数IObservable[]序列)
    {
    返回可观察的延迟(()=>
    {
    bool isEmpty=true;
    返回序列
    .Select(s=>s.Do(=>isEmpty=false))
    .选择(s=>Observable.If(()=>isEmpty,s))
    .Concat();
    });
    }
    

    Observable.Defer
    操作符用于防止多个订阅共享相同的
    bool isEmpty
    状态(有关此操作的更多信息).

    如果您能帮助我重新编写这个问题,我也将不胜感激——我已经尽了最大的努力,但这个问题很难用语言表达出来。如果源A返回结果,然后完成,会发生什么?转到来源B?还有,“新闻查询者”电话的签名是什么?它是否已经返回
    IObservable
    ?@JerKimball如果A返回结果,我们不关心abo
    Source A invoked
    Got value on source:Article from A
    Article from A
    Source completed, had values, so ending
    
    Source A invoked
    Source completed, no values, so moving to next source
    Source B invoked
    Got value on source:Article from B
    Article from B
    Source completed, had values, so ending
    
    Source A invoked
    Source completed, no values, so moving to next source
    Source B invoked
    Source completed, no values, so moving to next source
    Source C invoked
    Got value on source:Article from C
    Article from C
    Source completed, had values, so ending
    
    public static IObservable<T> SwitchIfEmpty<T>(this IObservable<T> @this, IObservable<T> switchTo)
    {
        if (@this == null) throw new ArgumentNullException(nameof(@this));
        if (switchTo == null) throw new ArgumentNullException(nameof(switchTo));
        return Observable.Create<T>(obs =>
        {
            var source = @this.Replay(1);
            var switched = source.Any().SelectMany(any => any ? Observable.Empty<T>() : switchTo);
            return new CompositeDisposable(source.Concat(switched).Subscribe(obs), source.Connect());
        });
    }
    
    /// <summary>Returns the elements of the first sequence, or the elements of the
    /// second sequence if the first sequence is empty.</summary>
    public static IObservable<T> SwitchIfEmpty<T>(this IObservable<T> first,
        IObservable<T> second)
    {
        return Observable.Defer(() =>
        {
            bool isEmpty = true;
            return first
                .Do(_ => isEmpty = false)
                .Concat(Observable.If(() => isEmpty, second));
        });
    }
    
    /// <summary>Returns the elements of the first non-empty sequence.</summary>
    public static IObservable<T> SwitchIfEmpty<T>(params IObservable<T>[] sequences)
    {
        return Observable.Defer(() =>
        {
            bool isEmpty = true;
            return sequences
                .Select(s => s.Do(_ => isEmpty = false))
                .Select(s => Observable.If(() => isEmpty, s))
                .Concat();
        });
    }