Skip to content

[Feature]: New higher-performance Filter() operators #758

@JakenVeina

Description

@JakenVeina

Describe the functionality desired 🐞

Consider the following new filtering operators, which should provide performance improvements over existing operators:

public static IObservable<IChangeSet<TObject, TKey>> Filter<TObject, TKey, TState>(
    this    IObservable<IChangeSet<TObject, TKey>>  source,
            IObservable<TState>                     state,
            Func<TState, TObject, bool>             predicate,
            bool                                    suppressEmptyChangeSets = true);

public static IObservable<IChangeSet<TItem>> Filter<TItem, TState>(
    this    IObservable<IChangeSet<TItem>>  source,
            IObservable<TState>             state,
            Func<TState, TItem, bool>       predicate,
            bool                            suppressEmptyChangeSets = true);

public static IObservable<IChangeSet<TObject, TKey>> FilterValues<TObject, TKey>(
    this    IObservable<IChangeSet<TObject, TKey>>  source,
            Func<TObject, bool>                     predicate,
            bool                                    suppressEmptyChangeSets = true);

Not set on the name for the third one, but it stems from a conversation with @RolandPheasant and @dwcullop in Slack. Alternatives for this would maybe be .FilterSlim() or .FilterDeterministic() or .FilterWithoutCaching(). Whatever the name, the goal would be to keep it consistent with other operators (like .Transform()) that normally keep an internal items cache, but could have versions that don't.

The steps the functionality will provide

For the first two operators, utilizing an IObservable<TState> parameter, the primary improvement operator is to reduce memory allocations required by existing operators that support dynamic filtering.

The third operator would improve performance for static-filtering scenarios, where the filter predicate is relatively fast, and is known to be deterministic, I.E. collection items are immutable, at least from the perspective of filtering. Under this scenario, .Filter() can be implemented without the need for an internal cache of items, becoming allocation-free for all single-change ChangeSets.

This operator would not be applicable as an ObservableList operator, as using deterministic-filtering does not eliminate the need for an internal items cache. An internal cache is still needed to track item indexes, so downstream indexes can be adjusted for items filtered out of the source.

Considerations

  • Consumers can currently use .Filter() with the IObservable<Func<TObject, bool>> or IObservable<Func<TObject, TKey, bool>> parameters to dynamically control filtering, but this requires either a new Func<> allocation for each change in filtering rules, or a decent bit of tedious code to reuse and re-emit a shared Func<> instance each time, while instead mutating its closure.
  • Consumers can currently use .FilterOnObservable(), but this requires multiple object allocations per-added-item, to create an IObservable<bool> that controls filtering of that item, and to subscribe to that stream. This approach also doesn't really support collection items being immutable, at all, as replace operations force a teardown and reconstruction of the filtering stream for that item.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions