.net Rx:是可观测的;“可重复”;比如IEnumerable,如果不是,这段代码是如何工作的?

.net Rx:是可观测的;“可重复”;比如IEnumerable,如果不是,这段代码是如何工作的?,.net,system.reactive,side-effects,.net,System.reactive,Side Effects,昨天我观看了第9频道的屏幕广播,Wes Dyer向我展示了如何使用拖拽实现拖放。我仍然不明白的是: 在放映结束时,Wes Dyer键入以下内容: var q = from start in mouseDown from delta in mouseMove.StartsWith(start).Until(mouseUp) .Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>

昨天我观看了第9频道的屏幕广播,Wes Dyer向我展示了如何使用拖拽实现拖放。我仍然不明白的是:

在放映结束时,Wes Dyer键入以下内容:

var q = from start in mouseDown
        from delta in mouseMove.StartsWith(start).Until(mouseUp)
                       .Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>
                           new { X = cur.X - prev.X, Y = cur.Y - prev.Y }))
        select delta;
简而言之,
q
是一个可观察对象,它将鼠标移动坐标增量推送到其订户

我不明白的是
mm.Zip(mm.Skip(1),…)
是如何工作的

据我所知,
IObservable
IEnumerable
的意义上是不可枚举的。由于
IEnumerable
的“pull”特性,它可以一次又一次地迭代,始终生成相同的项。(至少对于所有表现良好的可枚举项都是如此。)
IObservable
的工作方式不同。项目被推送到订阅服务器一次,就是这样。在上面的示例中,鼠标移动是单个事件,如果没有记录在内存中,则无法重复

那么,
.Zip
.Skip(1)
的组合如何可能起作用,因为它们处理的鼠标事件是单个的、不可重复的事件?此操作是否要求独立地“查看”两次
mm


以下是
Observable.Zip的方法签名供参考:

public static IObservable<TResult> Zip <TLeft, TRight, TResult>
(
    this IObservable<TLeft>       leftSource,     //  = mm
    IObservable<TRight>           rightSource,    //  = mm.Skip(1)
    Func<TLeft, TRight, TResult>  selector
)
publicstaticiobservable-Zip
(
此IObservable leftSource,//=mm
IObservable rightSource,//=mm.跳过(1)
函数选择器
)

附言:我刚刚看到另一个非常有洞察力的例子

项目被推送到订阅服务器一次,就是这样

是的,一个项目被推送一次,但该项目是一系列事件中的一个。这个序列仍然是一个序列。这就是Skip工作的原因——它跳过一个项目,然后在下一个项目出现时处理它(不跳过它)。

啊哈!我在P.S.中提到的这一点给了我一个重要的线索:
Zip
“记住”项目,以解释项目可能从一个可观察到的地方比从另一个地方更快到达的事实。我将尝试回答我的问题,如果我错了,我希望有人能纠正我

Zip
将来自两个可观察序列的输入配对,如下所示(字母和数字为“事件”):

它确实需要进行内部缓冲。在我发布的代码中,
mm
是真正的“活的”可观察到的<代码>mm.Skip(1)
类似于从中派生的状态机。亚历克斯·帕文的回答简要说明了这是如何工作的

因此,
mm.Zip(mm.Skip(1),…)
确实会查看
mm
两次,一次直接查看,一次通过
Skip(n)
过滤器查看。而且,由于可观测数据不是可重复的序列,因此它会进行内部缓冲,以说明一个序列比另一个序列更快地生成项目这一事实

(我用.NET Reflector快速浏览了一下Rx源代码,事实上,
Zip
包含一个
队列

这个操作不需要独立地“观察”mm两次吗

事实上,这就是你问题的答案:你可以多次订阅相同的
IObservable
序列

mm.Skip(1)
订阅
mm
,并将第一个值隐藏到自己的订阅者。 Zip是
mm.Skip(1)
mm
的订户。由于
mm
产生的值比
mm多一个。跳过(1)
,Zip始终在内部缓冲
mm
中的最后一个mousemove事件,以便将其与下一个未来的mousemove事件压缩。然后,选择器功能可以选择两者之间的增量

您应该注意的另一件事是(这是您问题标题的真实答案),这是可观察的
。FromEvent
-
IObservable
是可观察的热点,因此不可重复。但是也有冷的可观察的,实际上是可重复的,就像可观察的范围(0,10)。在后一种情况下,每个订阅服务器将接收相同的10个事件,因为它们是为每个订阅服务器独立生成的。对于mousemove事件,情况并非如此(您不会从过去获得鼠标移动事件)。但是因为Zip同时订阅了左右序列,所以在这种情况下可能是相同的

注意:您还可以装箱热/不可重复的
IEnumerable
:它不需要为每个枚举数返回相同的值。例如,您可以创建一个IEnumerable,它等待mousemove事件发生,然后生成该事件。在这种情况下,枚举数总是会阻塞(糟糕的设计),但这是可能的

@Nappy,谢谢你解释“热”和“冷”这两个术语。当我问这个问题时,我缺乏适当的词汇,这使得事情现在更清楚了。
mm                        ----A---------B-------C------D-----------E----->
                              |         |       |      |           |
                              |         |       |      |           |
mm.Skip(1)                ----+---------1-------2------3-----------4----->
                              |         |       |      |           |
                              |         |       |      |           |
mm.Zip(mm.Skip(1), ...)   ----+--------A,1-----B,2----C,3---------D,4---->