Subject<T>:
如果你要自己实现自定义IObservable <T>,你可能会发现当你想暴露Iobservable为public时仍然需要能够publish items到subscribers(订阅者)、当发生错误时抛出错误和发送完成时发送完成消息,和IObserver<T>所做的(OnNext()\OnError()\OnCompleted())一样。一个类型实现两个接口似乎有些奇怪,但它确实使事情变得更容易了,这就是 Subject<T>所能做的。
Subject<T>是最底层的 Subject,你可以有效的在一个返回 IObservable<T> 的方法后暴露你的Subject<T>,但在内部你仍然可以用 OnNext, OnError and OnCompleted方法来管理sequence(队列)
下面这个非常基础的例子中创建了一个subject,将它作为WriteSequenceToConsole()方法的参数传入,使其订阅到了它自身,并通过调用subject.OnNext(T)向sequence(队列,在此例中,也就是subject自身)发布了values(subject既是IObservable <T>又是IObserver<T>,自己订阅自己,自己发布给自己)
请注意,这个例子中的WriteSequenceToConsole方法只接受了一个IObservable<string>类型的参数,但是在IObservable<T>的介绍中我们看到Subscribe的参数类型应该IObserver<string>,这难道不会产生问题么?这是因为这里使用的是一个使用IObservable<T>类型参数的扩展方法(RX为IObservable<T>提供了一个使用Action<T>委托的扩展方法,该委托将在每次发布item时执行,并且扩展方法还有其他重载,允许为OnNext, OnCompleted 和OnError传递委托组合,也就不用IObserver<T>参数了)。
Subject<T>不同的分支,提供了略微不同实现。
ReplaySubject<T>:
ReplaySubject<T>可以缓存values并为任何之后的订阅重播。为了举例,我们将第一条发送代码移动到订阅之前:
其结果是b和c被打印出来,a则被忽略。再让我们来看一下使用ReplaySubject<T>的结果:
请注意,ReplaySubject<T>会在构造函数中创建一个实例来缓存发布的values。在许多情况下,这将造成不必要的内存压力,但可以通过设置缓存条数来解决这个问题。在下面的例子中,我们创建的缓存大小为2,因此只有后2个值被打印出来。
这里a已经被缓存删除,但b和c仍然有效,d在订阅之后发送,因此也被打印出来。
Output: b c d |
还有一种通过时间来设置缓存何时失效的办法,如下面的例子:
在上面的例子中,过期时间为150毫秒,w在执行时已经经过了200毫秒,因此被缓存删除而并没有被打印出来:
Output: x y z |
BehaviorSubject<T>:
BehaviorSubject<T>和ReplaySubject<T>类似,但它只缓存最后一个发送的值。它要求为其提供一个默认值,这意味着所有的subscribers (订阅者)将会立刻收到一个value,除非它已经Completed。在下面的例子中,a在没有调用OnNext方法进行发送的情况下被打印了出来:
在下面的例子中,b被打印了出来而不是a(因为在进行订阅之前调用了OnNext方法用新值b取代了构造函数中设置的默认值a):
在下面的例子中,b(取代了默认值)和c、d(在订阅之后)都被打印了出来,而传入构造函数的a则没有被打印出来:
在下面的例子中,没有任何值被打印出来,因为在订阅之前已经调用了OnCompleted():
这些例子说明ReplaySubject<T>和BehaviorSubject<T>之间的差异在于ReplaySubject<T>可以控制缓存的条数或者时间,而BehaviorSubject<T>则需要一个初始值,只要它还没有completed,那么就肯定会有一个value(即使在不调用OnNext的情况下)(ReplaySubject<T>则无法确定是否有value)(completed的BehaviorSubject<T>并不常见)。还有一个区别在于,ReplaySubject<T>将会在完成后仍然缓存其value,所以我们可以确定BehaviorSubject<T>完成后将没有任何值,而ReplaySubject<T>则可能有值。
BehaviorSubject<T>经常与类的属性有关,因为它们总是拥有一个值,并可以提供更改通知,所以它可以是将字段支持到属性的待选方案。
AsyncSubject<T>:
AsyncSubject<T>缓存value,但只缓存最后一个,并只在完成后发送。AsyncSubject<T>通常用于只发布一个值,然后立即完成。这意味着它很适合Task<T>。在下面的例子中,没有任何值被打印出来,因为没有调用OnCompleted方法:
在下面的例子中,C被打印出来,因为调用了OnCompleted方法:
Implicit contracts
在使用RX时有些隐性的关系需要注意,一个是一旦完成,就不会再有任何活动,完成方式有两种:OnCompleted() 或OnError(Exception),在本章中的4个例子都符合这一点,它们都忽略了任何已完成的队列发布的values,错误或者完成信息。在下面的例子中,我们试图将c打印出来,但发送c的OnNext方法在OnCompleted方法被调用之后,因而只有a和b被打印出来:
-------------------------------------------------------------------------------
题外话:其实我在这里发也仅仅是将电子文档中整理的文字复制黏贴过来,而电子文档中的效果类似这样:
更易于阅读,不是么?
同时我也做了思维导图,但是即使用PS做了处理也很难看清楚:
字看起来太小了……
所以以后有机会的话我会找个地方把文件也传上来,在这里看不清楚的可以直接看更新的文档。
暂无关于此日志的评论。