模拟IObservable的收益率回报

时间:2014-11-29 21:38:51

标签: c# linq system.reactive

当我不知道如何使用LINQ生成一些可枚举时,我只是创建自己的扩展方法并使用yield关键字。这给了我一个闭包,我可以存储诸如计数器或其他聚合值之类的东西。

IObservable中,我不知道是否有内置方法可以执行此操作。我最近想要生成一个observable,给定另一个IObservable<string>,忽略所有内容,直到源中出现一个起始值(例如&#34; start&#34;),然后开始忽略所有内容,直到源生成一个结尾价值(说&#34;结束&#34;)。

因此,如果我的源代码为{"1", "start", "2", "3", "end", "4", "start", "5", "end},则新的observable应为{"2", "3" "5"}

使用IObservable的内置int方法可能有一种方法可以做到这一点,但如果它是IEnumerable,那么使用yield关键字会很简单。所以我想知道是否有一种类似的直接方式来为IObservable做这件事。

我想出了一个基本上完成这项工作的小班:

public class ClosureSelectMany<TSource, TResult> : IObservable<TResult>
{
    private readonly IObservable<TSource> _Source;
    private readonly Func<Func<TSource, IObservable<TResult>>> _Selector;

    public ClosureSelectMany(IObservable<TSource> source, Func<Func<TSource, IObservable<TResult>>> selector)
    {
        _Source = source;
        _Selector = selector;
    }

    public IDisposable Subscribe(IObserver<TResult> observer)
    {
        var selector = _Selector();
        return _Source.SelectMany(selector).Subscribe(observer);
    }
}

public static class ObservableHelpers
{
    public static IObservable<TResult> ClosureSelectMany<TSource, TResult>(this IObservable<TSource> source, Func<Func<TSource, IObservable<TResult>>> selector)
    {
        return new ClosureSelectMany<TSource, TResult>(source, selector);
    }
}

我可以像以下一样使用它:

test = input.ClosureSelectMany<string, string>(() =>
{
    bool running = false;

    return val =>
    {
        if (val == "end")
            running = false;

        var result = running ? Observable.Return(val) : Observable.Empty<string>();

        if (val == "start")
            running = true;

        return result;
    };
});

如果我在IEnumerable中使用yield关键字,那么看起来就像我做的那样。

所以我想知道我是否重新发明轮子并且已经有一些内置功能可以做到这一点,如果不是为什么。也许这种方法可能会引起我现在没有看到的任何其他问题。

3 个答案:

答案 0 :(得分:2)

您可以使用Observable.Create而不是将现有的运算符组合在一起,但尽管可能,构图通常是一个更好的主意。

请注意,Create也有接受Task的重载 - 返回函数,允许您定义async iterator - 使用等待的协程,而不是 yield ,虽然这不是你特别需要的。

但是,您可以通过撰写ScanWhereSelect来解决您的问题:

xs.Scan(
  new { Running = false, Value = default(string) }, 
  (previous, current) => new
  {
    Running = current == "start" || (previous.Running && current != "end"), 
    Value = current
  })
  .Where(state => state.Running && state.Value != "start")
  .Select(state => state.Value);

答案 1 :(得分:2)

对于observable,没有yield return个等价物。它当然可以融入编译器,但目前还没有。

但是,使用可用的运算符,您可以轻松地执行所需的操作。

这对我有用:

var results =
    source
        .Publish(ss =>
            ss
                .Window(
                    ss.Where(s0 => s0 == "start"),
                    s => ss.Where(s1 => s1 == "end"))
                .Select(xs => xs.Skip(1).SkipLast(1))
                .Merge());

鉴于此来源:

var source = new []
{
    "1", "start", "2", "3", "end", "4", "start", "5", "end"
}.ToObservable();

我得到了这个输出:

2 
3 
5 

答案 2 :(得分:0)

首先,我想澄清一下,这里的重点是收益率回报的替代方案。

其次,这个特定的示例问题可以解决:

bool running = false;
Observable.ToObservable(new [] {"1", "start", "2", "3", "end", "4", "start", "5", "end"})
    .Where(s => {
        if (s == "start") running = true;            
        if (s == "end") running = false;                                    
        return (running && s != "start");
    })
    .Dump();

现在,回答:

你需要记住 yield return 只是语法糖,可以让你不必自己实现IEnumerable和IEnumerator。

您可以将它视为IEnumerator.MoveNext()和IEnumerator.Current的组合,而无需额外的类来保存枚举器的状态,如MSDN文档(https://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx)中所述。

您可能还读过IObservable是同步/推送IEnumerable(http://reactivex.io/intro.html)的异步/推送“ dual ”。

考虑到所有这些,您可以认为IObservable等效于yield return(IEnumerator.MoveNext()+ IEnumerator.Current)是 IObserver.OnNext(T值)方法。

以这种方式思考,您可以轻松实现您需要的内容(在LINQPad中测试):

void Main()
{
    var q = FilteredSource.Generate();
    q.Dump();
}

public class FilteredSource
{
    public static IObservable<string> Generate()
    {           
        var q = from s in OriginalSource.Generate()
                select s;

        return Observable.Create<string>(
            async observer =>
            {           
                bool produce = false;                
                try
                {
                    await q.ForEachAsync(s => {

                        if (s == "start")
                            produce = true;

                        if (s == "end")
                            produce = false;                        

                        if (produce && s != "start")
                            observer.OnNext(s);                        
                    });
                }
                catch (Exception ex)
                {
                    observer.OnError(ex);
                }

                observer.OnCompleted();

                return Disposable.Empty;
            });
    }
}

public class OriginalSource
{
    public static IObservable<string> Generate()
    {
        return Observable.Create<string>(
            observer =>
            {
                try
                {
                    string[] list = {"1", "start", "2", "3", "end", "4", "start", "5", "end"};
                    foreach (string s in list)
                        observer.OnNext(s);
                }
                catch (Exception ex)
                {
                    observer.OnError(ex);
                }

                observer.OnCompleted();

                return Disposable.Empty;
            });
    }            
}