feat(LinqMixins): System.Reactive/LINQ name layer + dedicated sinks#22
Merged
Conversation
Add 33 familiar System.Reactive/LINQ operator names (Select, Where, Scan, Aggregate, Merge, Concat, Amb, Switch, Zip, CombineLatest, WithLatestFrom, Do, DoWith, SelectWith, WhereWith, WhereNotNull, DistinctUntilChanged(+By), IgnoreElements, SelectMany, Delay, Timeout, Sample, Retry, Materialize, Dematerialize) as first-class operators. Each builds the SAME sink as its Primitives-named counterpart directly (no forwarding/redirect), so the two names are interchangeable with identical behaviour and allocation profile. Both name sets are fully supported. Replace closure-piggyback operator bodies with dedicated sinks so no per-call closure is allocated: MapWith/KeepWith/TapWith get internal *WithSignal sinks; Tap/Do reuse the existing TapSignal; Resume gets an internal ResumeSignal (shared with a future Catch(fallback)); Timestamp gets an internal TimestampSignal for the non-range path. Tests: genericified parity tests (RxNameParityTests) drive each operator pair once via TUnit [MethodDataSource] and assert each name matches the expected sequence and is behaviorally identical to its twin (unary, higher-order, binary, and virtual-clock time families). Add CPD exclusions for the deliberately duplicated Rx-name/sink files. Update the 3 API-approval snapshots and document the name layer in the README.
…are a sink The binary Concat (Rx name) was calling Signal.Chain (a Primitives-vocabulary factory) rather than constructing a sink directly. Add a two-source ChainSignal constructor and a matching ChainCoordinator.Run(first, second) that enqueues both inners directly, and have both the binary Concat and binary Chain build it — no FromEnumerable wrapper, no cross-vocabulary call. Every Rx name now constructs its sink directly.
…nd CPD exclusions Extract the interlocked two-slot subscription management that RecoverSignal and ResumeSignal both copied (Release/Assign + a per-class DisposedMarker) into a shared internal static SubscriptionSlots helper, reusing the existing public DisposedMarker sentinel. Eliminate TimestampSignal entirely: Timestamp is a stateful map, so it now builds MapWithSignal with a non-capturing CreateMoment selector. Remove the sink CPD exclusions added earlier (RecoverSignal/ResumeSignal no longer duplicate; TimestampSignal is gone) — only the deliberately-duplicated Rx-name operator-body file remains excluded.
…ayer collides The benchmark namespace nests under ReactiveUI.Primitives, so the new LinqMixins Rx-named extensions are preferred over the imported System.Reactive.Linq ones. Two SystemReactive competitor calls broke the build: Materialize (Spark vs Notification return type) and a Where whose lambda IDE0200 wanted removed (a false positive once our single-overload Where was selected). Call both via the RxObservable static alias so they unambiguously measure System.Reactive.
…titors The benchmark namespace nests under ReactiveUI.Primitives, so instance-style calls of the new Rx name-layer operators (Select/Where/Do/Scan/SelectMany/Materialize/ Dematerialize/IgnoreElements/Retry/Zip/CombineLatest, plus Buffer in one chain) silently bound to Primitives instead of System.Reactive — making the SystemReactive* benchmarks measure the wrong library. Rewrite those competitor calls in the explicit static form (RxObservable.Op(source, ...)) so they bind to System.Reactive. That static-call style is what RCS1196 flags, so it is disabled for the benchmark project via a scoped .editorconfig with a justification.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #22 +/- ##
==========================================
+ Coverage 91.70% 91.93% +0.23%
==========================================
Files 401 407 +6
Lines 15725 16019 +294
Branches 2276 2363 +87
==========================================
+ Hits 14420 14727 +307
+ Misses 985 971 -14
- Partials 320 321 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
… and Resume New-code coverage on the name layer was 60% (gate is 80%). Add: null-argument and out-of-range guard tests across all Rx names (covers the throw branches in RxNames, the bulk of the gap); stateful-sink value-then-error and throwing- projection tests (MapWith/KeepWith/TapWith OnError + catch paths); Resume fallback-on-error, plain-completion, and dispose tests (ResumeSignal); a Sample/Probe identity test and a 3-arg SelectMany/FlatMap parity test. New files now 83-100% covered.
Cover the remaining branches: the int-range fast paths (Zip/CombineLatest/ WithLatestFrom/Switch/Delay over ranges), the default-sequencer time overloads, Retry's happy path, and the rest of the null/right-operand guards in RxNames; the stateful sinks' null-observer rejection, post-terminal drop guards (via a manual source), and current-thread-requirement propagation (constructed directly); and ResumeSignal's null-observer, scheduled subscription path, and current-thread flag. RxNames/MapWith/KeepWith/TapWith/ResumeSignal/SubscriptionSlots are now 100%.
Codecov flagged the binary Chain first/second null checks (the Rx Concat nulls were tested, the deviant Chain ones were not). Adds both, bringing the patch to full coverage.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
Adds the familiar System.Reactive / LINQ operator names as a first-class surface alongside the Primitives vocabulary, and removes the closure-piggyback patterns behind several operators.
Name layer (33 operators)
Select,SelectWith,Where,WhereWith,WhereNotNull,Do,DoWith,Scan,Aggregate,DistinctUntilChanged(+By),IgnoreElements,SelectMany(×2),Merge,Concat(×2),Amb,Switch,Zip,CombineLatest,WithLatestFrom,Delay(×2),Timeout(×2),Sample(×2),Retry,Materialize,Dematerialize.Each name constructs the same sink directly as its Primitives-named counterpart (
Select↔Map,Where↔Keep,Scan↔Fold,Merge↔Blend,Zip↔Pair, …) — no forwarding, no redirect, identical behaviour and allocation profile, including the int-range fast paths. Both name sets are fully supported and interchangeable.Dedicated sinks (kill per-call closures)
Operators that were implemented as
source.Map(closure)/source.Recover(_ => fallback)etc. now build a purpose-built sink:MapWith/SelectWith→MapWithSignal,KeepWith/WhereWith→KeepWithSignal,TapWith/DoWith→TapWithSignal(internal, state stored on the sink).Tap/Doreuse the existingTapSignal.Resume→ new internalResumeSignal(holds the fallback directly; shared with a futureCatch(fallback)).Timestampnon-range path → new internalTimestampSignal.All new sinks are
internal(no public-API churn; the 3-paramMapWithsink avoids CA1005).Tests
RxNameParityTests— genericified: each operator pair is one[MethodDataSource]row consumed by a single test body that asserts (a) each name matches the expected sequence and (b) the two names are behaviorally identical. Four families: unaryIObservable<int>→IObservable<int>, higher-order source-of-sources, binary (manual-subject drive scripts), and time-based (virtualTestClock). 24 cases, green on net8/net9/net10. Full suite 265/265 on net9; core builds clean on net8.0/net9.0/net462.Housekeeping
sonarcloud.ymlfor the deliberately-duplicated Rx-name/sink files.LinqMixinsmethods; no internal sinks leaked).