feat(LinqMixins): add fused BlendUnique operator (merge + distinct-until-changed)#18
Conversation
…til-changed) Folds a concurrent merge of a fixed set of sources and distinct-until-changed into a single sink, avoiding the extra subscription hop and allocation of sources.Blend().Unique(). Forwards the first source error and completes once every source has completed; an optional comparer suppresses duplicates.
There was a problem hiding this comment.
Pull request overview
Adds a new fused operator LinqMixins.BlendUnique<T> to merge a fixed set of IObservable<T> sources while applying distinct-until-changed semantics in a single sink, reducing the extra hop/allocation of sources.Blend().Unique().
Changes:
- Added
LinqMixins.BlendUnique<T>overloads and a dedicated fused sink implementation. - Added
BlendUniqueTestscovering core behavior (merge + adjacent duplicate suppression, empty sources, comparer, error, null validation). - Updated the net9.0 API approval snapshot to include the new public APIs.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/ReactiveUI.Primitives/SignalOperatorMixins.BlendUnique.cs | Implements the fused merge + distinct-until-changed operator and its sink. |
| src/tests/ReactiveUI.Primitives.Tests/BlendUniqueTests.cs | Adds unit tests for BlendUnique behavior and argument validation. |
| src/tests/ReactiveUI.Primitives.Tests/ApiApprovalTests.Primitives.DotNet9_0.verified.txt | Records the new public API surface for net9.0 approval testing. |
…ce completion, add net8/net10 API snapshots - BlendUnique now throws ArgumentNullException for a null source element at call time (matching the repo's params-factory convention) instead of erroring at subscription; drops the now-redundant in-sink null check. - Empty-source path completes directly instead of decrementing the active counter below zero. - Add a null-source-element test case. - Record the new BlendUnique API surface in the net8.0 and net10.0 approval snapshots (only net9.0 was updated).
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #18 +/- ##
==========================================
+ Coverage 91.53% 91.62% +0.08%
==========================================
Files 398 400 +2
Lines 15481 15613 +132
Branches 2231 2259 +28
==========================================
+ Hits 14171 14305 +134
+ Misses 990 987 -3
- Partials 320 321 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Adds DisposeUnsubscribesFromSources and SuppressesNotificationsAfterTerminalError (value/completion/error all suppressed once a source has errored, via a third source) and drops the unreachable done-check in the empty-source path, bringing BlendUnique to full line coverage.
The scheduler-routed WaitFor* tests schedule the subscribe on a sequencer and then block the calling thread on done.Wait(timeout). Routing through TaskPoolSequencer means the scheduled subscribe needs a thread-pool thread while the test thread is blocked; under parallel test load on Windows CI's few cores the pool starves and the waits time out spuriously. Route these tests through ImmediateSequencer so the subscribe runs inline before the blocking wait — the non-null-scheduler code path is still exercised, deterministically and without a thread-pool dependency.
|



What
Adds
LinqMixins.BlendUnique<T>— a single fused sink that concurrently merges a fixed set of sources and applies distinct-until-changed, in one subscription hop:Semantics: forwards a merged value only when it differs from the previously forwarded one; forwards the first source error and suppresses later notifications; completes once every source has completed. An optional comparer controls duplicate suppression (defaults to
EqualityComparer<T>.Default).Why
sources.Blend().Unique()is two sinks — a Blend coordinator plus a Unique observer — so it costs an extra subscription hop and allocation on a hot path. Activation/lifecycle code (merge several boolean event streams, then distinct) wants this as one allocation-light pass.BlendUniquefolds the merge and the distinct into a single sink.Tests
New
BlendUniqueTests(5 cases): merge + consecutive-duplicate suppression, empty-source completion, custom comparer, first-source-error propagation, null-argument validation. FullReactiveUI.Primitives.Testssuite green on net9.0; core builds on net8.0/net9.0/net462. API approval snapshot updated for the two new public methods.