使用LINQ运算符查询可观察序列

来源:互联网 发布:java用户界面布局 编辑:程序博客网 时间:2024/05/16 14:51

  在与现有.NET事件桥接时,我们已将现有.NET事件转换为可观察序列以订阅它们。 在本主题中,我们将观察可观察序列的一类本质作为IObservable 对象,其中通用的LINQ操作符由Rx程序集提供以操作这些对象。 大多数运算符采用可观察序列并对其执行一些逻辑并输出另一个可观察序列。 此外,从代码示例中可以看出,您甚至可以在源序列上链接多个运算符,以根据您的确切要求调整结果序列。

使用不同的运算符

  我们已经使用了前面主题中的Create和Generate运算符来创建和返回简单序列。 我们还使用FromEventPattern运算符将现有的.NET事件转换为可观察的序列。 在本主题中,我们将使用Observable类型的其他静态LINQ运算符,以便可以对数据进行过滤,分组和转换。 这样的算子将可观察序列作为输入,并产生可观测序列作为输出。

组合不同的序列

  在本节中,我们将研究一些将各种可观察序列组合成单个可观察序列的运算符。 请注意,当我们组合序列时,数据不会被转换。
  在下面的示例中,我们使用Concat运算符将两个序列组合成一个序列并订阅。 为了说明的目的,我们将使用非常简单的Range(x,y)运算符来创建一个以x开头的整数序列,然后生成y个序列号。

var source1 = Observable.Range(1, 3);var source2 = Observable.Range(1, 3);source1.Concat(source2)       .Subscribe(Console.WriteLine);Console.ReadLine();

  
  注意,结果序列是1,2,3,1,2,3。这是因为当您使用Concat运算符时,第二个序列(source2)直到第一个序列(source1)完成推送其所有值后才会被激活。只有在source1完成后,source2才会开始将值推送到结果序列。然后,订户将从结果序列中获得所有值。
  将此与合并运算符进行比较。如果运行以下示例代码,您将获得1,1,2,2,3,3。这是因为两个序列同时是活动的,并且值在源中发生时被推出。结果序列仅在最后一个源序列完成推送值时完成。
注意,为了合并工作,所有源可观察序列需要具有相同类型的IObservable 。所得到的序列将具有类型IObservable 。如果source1在序列中间产生一个OnError,那么结果序列将立即完成。

var source1 = Observable.Range(1, 3);var source2 = Observable.Range(1, 3);source1.Merge(source2)       .Subscribe(Console.WriteLine);Console.ReadLine();

可以使用Catch运算符进行另一个比较。 在这种情况下,如果source1完成没有任何错误,则source2将不会启动。 因此,如果运行以下示例代码,您将只得到1,2,3,因为source2(它产生4,5,6)被忽略。

var source1 = Observable.Range(1, 3);var source2 = Observable.Range(4, 3);source1.Catch(source2)       .Subscribe(Console.WriteLine);Console.ReadLine();

  最后,让我们来看看OnErrorResumeNext。 此操作符将移动到source2,即使source1由于错误而无法完成。 在以下示例中,即使source1表示以异常(通过使用Throw运算符)终止的序列,订阅方将接收由source2发布的值(1,2,3)。 因此,如果您期望源序列产生任何错误,则更安全的是使用OnErrorResumeNext来保证订户仍然会收到一些值。

var source1 = Observable.Throw<int>(new Exception("An error has occurred."));var source2 = Observable.Range(4, 3);source1.OnErrorResumeNext(source2)       .Subscribe(Console.WriteLine);Console.ReadLine();

注意,对于所有这些组合运算符工作,所有可观察的序列需要是相同类型的T.

投影

Select操作符可以将可观察序列的每个元素转换为另一种形式。
在下面的例子中,我们分别将整数序列投影成长度为n的字符串。

var seqNum = Observable.Range(1, 5);var seqString = from n in seqNum                select new string('*', (int)n);seqString.Subscribe(str => { Console.WriteLine(str); });Console.ReadKey();

在下面的示例中,它是.NET事件转换示例的扩展,我们在“使用现有.NET事件桥接”主题中看到,我们使用选择运算符将IEventPattern 数据类型投影到Point类型中。 这样,我们将鼠标移动事件序列转换为可以进一步解析和处理的数据类型,如下一个“过滤”部分所示。

var frm = new Form();IObservable<EventPattern<MouseEventArgs>> move = Observable.FromEventPattern<MouseEventArgs>(frm, "MouseMove");IObservable<System.Drawing.Point> points = from evt in move                                          select evt.EventArgs.Location;points.Subscribe(pos => Console.WriteLine("mouse at " + pos));Application.Run(frm);

  最后,让我们看看SelectMany运算符。 SelectMany运算符有很多重载,其中一个接受选择器函数参数。这个选择器函数在由source observable推出的每个值上调用。对于这些值中的每一个,选择器将它投射到迷你可观察序列中。最后,SelectMany操作符将所有这些迷你序列平坦化为单个结果序列,然后将其推送到订阅者。
  SelectMany返回的observable在源序列之后发布OnCompleted,并且选择器生成的所有mini observable序列都已完成。当源流中发生错误时,选择器函数抛出异常时,或者在任何mini observable序列中发生错误时,将触发OnError。
  在下面的示例中,我们首先创建一个源序列,每5秒产生一个整数,然后决定采用生成的前2个值(通过使用Take运算符)。然后,我们使用SelectMany来使用另一个{100,101,102}序列来投影这些整数。通过这样做,产生两个小的可观察序列,{100,101,102}和{100,101,102}。这些最终被平坦化成{100,101,102,100,101,102}的单个整数流,并被推送到观察者。

var source1 = Observable.Interval(TimeSpan.FromSeconds(5)).Take(2);var proj = Observable.Range(100, 3);var resultSeq = source1.SelectMany(proj);var sub = resultSeq.Subscribe(x => Console.WriteLine("OnNext : {0}", x.ToString()),                              ex => Console.WriteLine("Error : {0}", ex.ToString()),                              () => Console.WriteLine("Completed"));Console.ReadKey();

过滤

在下面的示例中,我们使用主要的运算符创建一个简单的可观察数字序列。 主要的运算符有几个重载。 在我们的示例中,它采用初始状态(在我们的示例中为0),终止的条件函数(少于10次),迭代器(+1),结果选择器(当前值的平方函数)。 ,并使用Where和Select运算符仅打印小于15的值。

IObservable<int> seq = Observable.Generate(0, i => i < 10, i => i + 1, i => i * i);IObservable<int> source = from n in seq                          where n < 5                          select n;source.Subscribe(x => {Console.WriteLine(x);});   // output is 0, 1, 4, 9Console.ReadKey();

以下示例是您在本主题前面看到的投影示例的扩展。 在该示例中,我们使用Select运算符将IEventPattern 数据类型投影到Point类型中。 在下面的示例中,我们使用Where和Select运算符仅选择我们感兴趣的那些鼠标移动。 在这种情况下,我们将鼠标移动到第一平分线上(其中x和y坐标相等)。

var frm = new Form(); IObservable<EventPattern<MouseEventArgs>> move = Observable.FromEventPattern<MouseEventArgs>(frm, "MouseMove");IObservable<System.Drawing.Point> points = from evt in move                                          select evt.EventArgs.Location;var overfirstbisector = from pos in points                        where pos.X == pos.Y                         select pos;var movesub = overfirstbisector.Subscribe(pos => Console.WriteLine("mouse at " + pos));Application.Run(frm);

基于时间的操作

您可以使用Buffer运算符来执行基于时间的操作。
缓冲可观察序列意味着可观察序列的值基于指定的时间跨度或计数阈值被放入缓冲器中。这在您期望大量数据被序列推出并且订户没有用于处理这些值的资源的情况下尤其有用。通过基于时间或计数缓冲结果,并且当超过标准时(或当源序列已经完成时),仅返回值序列,订户可以以其自己的速度处理OnNext调用。
在下面的示例中,我们首先为每秒创建一个简单的整数序列。然后,我们使用Buffer运算符并指定每个缓冲区将保存来自序列的5个项目。当缓冲区已满时,调用OnNext。然后,我们使用Sum运算符计算缓冲区的总和。缓冲区将自动刷新,并开始另一个循环。打印输出将为10,35,60 …其中10 = 0 + 1 + 2 + 3 + 4,35 = 5 + 6 + 7 + 8 + 9,依此类推。

var seq = Observable.Interval(TimeSpan.FromSeconds(1));var bufSeq = seq.Buffer(5);bufSeq.Subscribe(values => Console.WriteLine(values.Sum()));Console.ReadKey();

我们还可以创建一个具有指定时间跨度的缓冲区。 在以下示例中,缓冲区将保存已累积3秒钟的项目。 打印输出将为3,12,21 …其中3 = 0 + 1 + 2,12 = 3 + 4 + 5,依此类推。

var seq = Observable.Interval(TimeSpan.FromSeconds(1));var bufSeq = seq.Buffer(TimeSpan.FromSeconds(3));bufSeq.Subscribe(value => Console.WriteLine(value.Sum()));  Console.ReadKey();

注意,如果你使用Buffer或Window,你必须确保序列在过滤之前不为空。

LINQ操作符

LINQ Operators by Categories主题列出了Observable类型按其类别实现的所有主要LINQ操作符; 具体地:创建,转换,组合,功能,数学,时间,异常,杂项,选择和原语。

0 0
原创粉丝点击