我有一个可观察的序列,应根据某些条件使用Where
运算符进行过滤,并应使用这些过滤的元素填充列表。
我想动态更改过滤条件,因此当发生这种情况时,应清除列表,应评估可观察序列的先前元素的新标准,以及序列生成的新元素。
列表应该使用过去的元素重新填充,这些元素现在通过新标准,继续使用新元素(也可以过滤)。我并不关心新元素可能会被延迟,而之前的元素会被重新评估,但我确实关心订单。
这可以通过Reactive Extensions完成吗?
答案 0 :(得分:3)
这是一个很好的扩展方法,可以满足您的需求:
public static IObservable<T> Refilterable<T>(
this IObservable<T> source, IObservable<Func<T, bool>> filters)
{
return
Observable
.Create<T>(o =>
{
var replay = new ReplaySubject<T>();
var replaySubscription = source.Subscribe(replay);
var query = filters.Select(f => replay.Where(f)).Switch();
var querySubscription = query.Subscribe(o);
return new CompositeDisposable(replaySubscription, querySubscription);
});
}
我用这段代码测试了这个:
var source = new Subject<int>();
var filters = new Subject<Func<int, bool>>();
var subscription = source.Refilterable(filters).Subscribe(x => Console.WriteLine(x));
source.OnNext(1);
source.OnNext(2);
source.OnNext(3);
filters.OnNext(x => x % 2 == 0);
source.OnNext(4);
source.OnNext(5);
filters.OnNext(x => x % 2 == 1);
source.OnNext(6);
filters.OnNext(x => x % 3 == 0);
source.OnNext(7);
filters.OnNext(x => x % 2 == 1);
subscription.Dispose();
filters.OnNext(x => x % 2 == 0);
我得到了这个输出:
2 4 1 3 5 3 6 1 3 5 7
这似乎是你追求的目标。
我刚刚注意到了生成列表的要求。这是一个更新:
public static IObservable<IList<T>> Refilterable<T>(this IObservable<T> source, IObservable<Func<T, bool>> filters)
{
return
Observable
.Create<IList<T>>(o =>
{
var replay = new ReplaySubject<T>();
var replaySubscription = source.Subscribe(replay);
var query =
filters
.Select(f =>
replay
.Synchronize()
.Where(f)
.Scan(new List<T>(), (a, x) =>
{
a.Add(x);
return a;
}))
.Switch();
var querySubscription = query.Subscribe(o);
return new CompositeDisposable(replaySubscription, querySubscription);
});
}
我注意到的另一件事是VB.NET标签。如果需要,我会看看我是否可以转换。
这应该是正确的VB:
<System.Runtime.CompilerServices.Extension> _
Public Shared Function Refilterable(Of T)(source As IObservable(Of T), filters As IObservable(Of Func(Of T, Boolean))) As IObservable(Of IList(Of T))
Return Observable.Create(Of IList(Of T))( _
Function(o)
Dim replay = New ReplaySubject(Of T)()
Dim replaySubscription = source.Subscribe(replay)
Dim query = filters.[Select](Function(f) replay.Synchronize().Where(f).Scan(New List(Of T)(), _
Function(a, x)
a.Add(x)
Return a
End Function)).Switch()
Dim querySubscription = query.Subscribe(o)
Return New CompositeDisposable(replaySubscription, querySubscription)
End Function)
End Function
答案 1 :(得分:1)
除非我遗漏了一些重要的内容,否则指定的需求将消除对不可变集合的需求并简化实现,因为您需要缓冲所有发出的值。
private List<T> values = new List<T>();
private IObservable<T> _valueSource;
public List<T> ValidValues => values.Where(MatchesCriteria).ToList();
private void StartSubscriptions()
{
var addNewValuesSub = _valueSource.Subscribe(values.Add); //todo disposing
}
如果您认为IEnumerable.Where
太慢,我们事先知道所有可能的标准。我们可以GroupBy
将值分别添加到各自的Observable
s /数据结构中。它看起来像这样。
_valueSource.GroupBy(CriteriaSelector)
.Subscribe(i => UpdateDataStructure(i.Key(), i.Latest()) );
IObservable
不适合缓冲大量值,以后可以通过某些条件访问这些值。这是IEnumerable
的工作。
最后,如果您认为内存使用会成为问题,请考虑将values
重构为内存对象缓存系统。
答案 2 :(得分:1)
我有两个初步反应
Replay()
为了解决我的第一个想法,你可以拥有这个代码
public class Foo
{
private readonly IDisposable _replayConnection;
private readonly IConnectableObservable<int> _replaySource;
private readonly SerialDisposable _subscription = new SerialDisposable();
private readonly List<int> _values = new List<int>();
//the Ctor or some initialise method...
public Foo(IObservable<int> source)
{
_replaySource = source.Replay();
_replayConnection = _replaySource.Connect()
}
public void SetFilter(Func<int, bool> predicate)
{
//Not thread safe. If required, then a scheduler can solve that.
_values.Clear();
_subscription.Disposable = _replaySource.Where(predicate)
.Subscribe(value => _values.Add(value),
ex => {/*Do something here to handle errors*/},
() => {/*Do something here if you want to cater for completion of the sequence*/},
}
}
然而,这只是让我更关注点 2 。如果您期望数百万的值,那么如果它们只是整数,那么您将使用大约3MB / 1mil的内存项。如果它们是整数,则按值语义复制,您将在重放缓冲区和最终列表(IIRC)中获得副本。如果这种内存压力还可以,那么我认为上面的Replay
代码会没问题。另请注意,如果您尝试缓冲超过int.MaxValue
值,则会抛出此重播用法。