Modernize async library and test fixes#126
Conversation
Update package versions in Directory.Packages.props (Polyfill -> 9.12.0, Microsoft.NET.Test.Sdk -> 18.3.0, TUnit -> 1.17.54, Verify.TUnit -> 31.13.2, Microsoft.Testing.Extensions.CodeCoverage -> 18.5.1, etc.) and remove some older test package entries. Refactor ReactiveExtensionsTests to reliably track and await async subscription handlers: replace inline async Subscribe callbacks with a tasks list and a local HandleAsync wrapper that disposes the sync token in a finally block, then await Task.WhenAll(tasks) to avoid race conditions and flakiness in the tests.
Change SyncTimer to key timers by both TimeSpan and IScheduler, add an overload SyncTimer(TimeSpan, IScheduler) and keep a fallback to Scheduler.Default. Validate scheduler is non-null and create the underlying Observable.Timer with the provided scheduler so timers can be shared per scheduler. Update tests to use TestScheduler (pass it into SyncTimer and OnErrorRetry), and replace async wait helpers with scheduler.AdvanceBy to make timing deterministic.
Apply modern C# updates and various test fixes across the async library. Highlights: - Update .github documentation target frameworks and add RCS1047 to NoWarn in Directory.Build.props. - Bump copyright years from 2019-2025 to 2019-2026 in many source files. - Add conditional ArgumentNullException.ThrowIfNull / ThrowIfLessThan checks for .NET 8+ and use Lock on newer frameworks where appropriate to improve null-safety and reliability. - Expand ConcurrentObserverCallsException into a full exception class with standard constructors. - Minor API/implementation cleanups: suppress CA2000 where needed, simplify nullable/collection initializations, expression-bodied test methods, additional disposals in tests, and other small refactors to reduce analyzer warnings and improve robustness.
|
Just turn off RCS1047 in the editorconfig if you want to disable it everywhere rather than NoWarn |
There was a problem hiding this comment.
Pull request overview
Modernizes ReactiveUI.Extensions (including the async observable/subject/operator layer) by adopting newer C#/.NET patterns (NET8+ argument guards, NET9 Lock usage), updating analyzer configuration/docs, and adjusting tests for improved reliability.
Changes:
- Add conditional NET8+ argument/range guards (
ThrowIfNull/ThrowIfLessThan*) and adopt NET9Lockin several synchronization gates. - Expand
ConcurrentObserverCallsExceptioninto a fuller exception type with standard constructors. - Update copyrights/analyzer configuration and refine tests (timing, disposal, small refactors).
Reviewed changes
Copilot reviewed 151 out of 151 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/stylecop.json | Updates copyright year template. |
| src/ReactiveUI.Extensions/Usings.cs | Adjusts global usings to support moved/modernized references. |
| src/ReactiveUI.Extensions/ReactiveExtensions.cs | Adds NET8+ argument guards; uses NET9 Lock gates in several operators; minor doc/comment change. |
| src/ReactiveUI.Extensions/Internal/Stale.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Internal/IEnumerableIList{T}.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Internal/Heartbeat.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Internal/EnumeratorIList.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Internal/EnumerableIList{T}.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Internal/EnumerableIList.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Internal/ConcurrencyLimiter.cs | Uses NET9 Lock for synchronization gate; rename _locker → _gate. |
| src/ReactiveUI.Extensions/IStale.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/IHeartbeat.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Continuation.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/UnhandledExceptionHandler.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/SubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/SerialSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/SerialStatelessSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/SerialStatelessReplayLastSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/SerialReplayLatestSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/Options/SubjectCreationOptions.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/Options/ReplayLatestSubjectCreationOptions.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/Options/PublishingOption.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/Options/BehaviorSubjectCreationOptions.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/ISubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/ConcurrentSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/ConcurrentStatelessSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/ConcurrentStatelessReplayLatestSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/ConcurrentReplayLatestSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/Base/Concurrent.cs | Adds NET8+ null checks for observer lists. |
| src/ReactiveUI.Extensions/Async/Subjects/Base/BaseSubjectAsync.cs | Uses NET9 Lock gate; adds NET8+ null guard in subscribe core. |
| src/ReactiveUI.Extensions/Async/Subjects/Base/BaseStatelessSubjectAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Subjects/Base/BaseStatelessReplayLastSubjectAsync.cs | Adds NET8+ null guard in subscribe core. |
| src/ReactiveUI.Extensions/Async/Subjects/Base/BaseReplayLatestSubjectAsync.cs | Adds NET8+ null guard in subscribe core. |
| src/ReactiveUI.Extensions/Async/Operators/Zip.cs | Adds NET8+ null guards; uses NET9 Lock inside Zip state. |
| src/ReactiveUI.Extensions/Async/Operators/Yield.cs | Adds NET8+ null guard. |
| src/ReactiveUI.Extensions/Async/Operators/Wrap.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Where.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/WaitCompletionAsync.cs | Adds NET8+ null guard. |
| src/ReactiveUI.Extensions/Async/Operators/Using.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/ToListAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/ToDictionaryAsync.cs | Adds NET8+ null guards for selectors. |
| src/ReactiveUI.Extensions/Async/Operators/ToAsyncEnumerable.cs | Adds NET8+ null guards for source/factory. |
| src/ReactiveUI.Extensions/Async/Operators/Timeout.cs | Adds NET8+ range/null guards; uses NET9 Lock gate in observer. |
| src/ReactiveUI.Extensions/Async/Operators/Throttle.cs | Adds NET8+ range guard. |
| src/ReactiveUI.Extensions/Async/Operators/TakeWhile.cs | Adds NET8+ null guards for predicates. |
| src/ReactiveUI.Extensions/Async/Operators/TakeUntilOptions.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/TakeUntil.cs | Adds NET8+ null guards; minor expression-bodied refactor. |
| src/ReactiveUI.Extensions/Async/Operators/Take.cs | Adds NET8+ range guard. |
| src/ReactiveUI.Extensions/Async/Operators/SwitchObservable.cs | Uses NET9 Lock gate in subscription. |
| src/ReactiveUI.Extensions/Async/Operators/Switch.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/SubscribeAsync.cs | Adds NET8+ null guards for source/delegates. |
| src/ReactiveUI.Extensions/Async/Operators/StartWith.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/SkipWhile.cs | Adds NET8+ null guards for predicates. |
| src/ReactiveUI.Extensions/Async/Operators/Skip.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/SingleOrDefaultAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/SingleAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/SelectMany.cs | Adds NET8+ null guards for selectors. |
| src/ReactiveUI.Extensions/Async/Operators/Select.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Scan.cs | Adds NET8+ null guards for accumulator. |
| src/ReactiveUI.Extensions/Async/Operators/Retry.cs | Adds NET8+ range guard. |
| src/ReactiveUI.Extensions/Async/Operators/RefCount.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Prepend.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/OnErrorResumeAsFailure.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/OnDispose.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/OfType.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/ObserveOnAsyncObservable.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/ObserveOn.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Multicast.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Merge.cs | Minor expression-bodied refactor in nested observer disposal. |
| src/ReactiveUI.Extensions/Async/Operators/LongCountAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/LastOrDefaultAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/LastAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/GroupedAsyncObservable.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/GroupBy.cs | Adds NET8+ null guards for source/selector. |
| src/ReactiveUI.Extensions/Async/Operators/ForEachAsync.cs | Adds NET8+ null guard for action. |
| src/ReactiveUI.Extensions/Async/Operators/FirstOrDefaultAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/FirstAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Do.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/DistinctUntilChanged.cs | Adds NET8+ null guards for comparer/selectors. |
| src/ReactiveUI.Extensions/Async/Operators/Distinct.cs | Adds NET8+ null guards for selector/comparer. |
| src/ReactiveUI.Extensions/Async/Operators/Delay.cs | Adds NET8+ range guard. |
| src/ReactiveUI.Extensions/Async/Operators/CountAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/ContainsAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/ConcatObservablesObservable{T}.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/ConcatEnumerableObservable{T}.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Concat.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/CompletionObservableDelegate.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/CombineLatest.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/Catch.cs | Adds NET8+ null guards for source/handler. |
| src/ReactiveUI.Extensions/Async/Operators/Cast.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Operators/AnyAllAsync.cs | Adds NET8+ null guard for predicate. |
| src/ReactiveUI.Extensions/Async/Operators/AggregateAsync.cs | Adds NET8+ null guards; minor parameter naming cleanup. |
| src/ReactiveUI.Extensions/Async/ObserverAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/ToAsyncObservable.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/Timer.cs | Adds NET8+ range guards for dueTime/period. |
| src/ReactiveUI.Extensions/Async/Observables/Throw.cs | Adds NET8+ null guard for error. |
| src/ReactiveUI.Extensions/Async/Observables/Return.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/Range.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/Never.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/Interval.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/FromAsync.cs | Adds NET8+ null guard for factory. |
| src/ReactiveUI.Extensions/Async/Observables/Empty.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/Defer.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Observables/Create.cs | Adds NET8+ null guard; minor refactor in background scheduling lambda. |
| src/ReactiveUI.Extensions/Async/ObservableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Mixins/SubjectMixins.cs | Adds NET8+ null guards for subject/mapper. |
| src/ReactiveUI.Extensions/Async/Mixins/OptionalMixins.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Mixins/DisposableAsyncMixins.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Mixins/AsyncContextMixins.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/WrappedObserverAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/TaskObserverAsyncBase.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/Result.cs | Adds NET8+ null guard for exception. |
| src/ReactiveUI.Extensions/Async/Internals/Optional.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/MulticastObservableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/CancelableTaskSubscription{T}.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/CancelableTaskSubscription.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/AsyncGate.cs | Uses NET9 Lock for internal gate. |
| src/ReactiveUI.Extensions/Async/Internals/AnonymousObserverAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Internals/AnonymousObservableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/IObserverAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/IObservableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Disposables/SingleAssignmentDisposableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Disposables/SerialDisposableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Disposables/DisposableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/Disposables/CompositeDisposableAsync.cs | Uses NET9 Lock; adds NET8+ guards; minor list initialization refactor. |
| src/ReactiveUI.Extensions/Async/ConnectableObservableAsync.cs | Copyright year bump. |
| src/ReactiveUI.Extensions/Async/ConcurrentObserverCallsException.cs | Expands exception type with additional constructors. |
| src/ReactiveUI.Extensions/Async/Bridge/ObservableBridgeExtensions.cs | Adds NET8+ guards; adds CA2000 suppression; uses NET9 Lock; minor disposal/refactor. |
| src/ReactiveUI.Extensions/Async/AsyncContext.cs | Adds NET8+ null guards for factory methods/continuation. |
| src/ReactiveUI.Extensions.Tests/ReactiveExtensionsTests.cs | Improves async test timing robustness by separating wait conditions. |
| src/ReactiveUI.Extensions.Tests/DisposableExtensionsTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/TransformationOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/TimeBasedOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/TerminalOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/TakeUntilOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/SubjectTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/ResultAndInfrastructureTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/OperatorEdgeCaseTests.cs | Adds extra disposals; converts some tests to expression-bodied; minor refactors. |
| src/ReactiveUI.Extensions.Tests/Async/FilteringOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/FactoryObservableTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/ErrorHandlingOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/DisposableTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/DeepCoverageTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/CoverageBoostTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/CombiningOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/CombineLatestOperatorTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/BridgeTests.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/AsyncTestHelpers.cs | Copyright year bump. |
| src/ReactiveUI.Extensions.Tests/Async/AdditionalOperatorTests.cs | Copyright year bump. |
| src/Directory.Build.props | Adds RCS1047 to NoWarn; defines TFMs; confirms analyzers/lang version settings. |
| .github/copilot-instructions.md | Updates documented target frameworks list. |
Comments suppressed due to low confidence (1)
src/ReactiveUI.Extensions/ReactiveExtensions.cs:1322
- The XML doc comment contains a malformed apostrophe character ("hasn�t"), which can render incorrectly in generated docs and is likely an encoding artifact. Replace it with a standard ASCII apostrophe ("hasn't").
|
@ChrisPulman I've opened a new pull request, #127, to work on those changes. Once the pull request is ready, I'll request review from you. |
Introduce Internal/ArgumentExceptionHelper with ThrowIfNull and ThrowIfNullOrEmpty helpers and replace scattered NET8/legacy null-check conditionals across the Async and Reactive extensions with calls to ArgumentExceptionHelper.ThrowIfNull. This centralizes argument validation (removing many #if NET8_0_OR_GREATER blocks), streamlines a few checks (e.g. Timeout and WaitForCondition), and reduces duplication across numerous operator/subject/observable implementations.
…ensions into CP_UpdatePackages
…rCallsException (#127) * Initial plan * Add [Serializable] attribute and serialization constructor to ConcurrentObserverCallsException Co-authored-by: ChrisPulman <4910015+ChrisPulman@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ChrisPulman <4910015+ChrisPulman@users.noreply.github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #126 +/- ##
==========================================
+ Coverage 76.48% 78.22% +1.74%
==========================================
Files 117 117
Lines 4796 4757 -39
Branches 723 636 -87
==========================================
+ Hits 3668 3721 +53
+ Misses 809 761 -48
+ Partials 319 275 -44 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@ChrisPulman I've opened a new pull request, #128, to work on those changes. Once the pull request is ready, I'll request review from you. |
Set dotnet_diagnostic.RCS1047.severity = none in .editorconfig and remove RCS1047 from the NoWarn list in src/Directory.Build.props. This centralizes the analyzer configuration in .editorconfig and avoids duplicate suppression in MSBuild.
…ensions into CP_UpdatePackages
…ert callback invocation (#128) * Initial plan * Add disposeCallbackInvoked flag assertion in WhenOnDisposeAsyncSourceFails_ThenCallbackInvoked test Co-authored-by: ChrisPulman <4910015+ChrisPulman@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ChrisPulman <4910015+ChrisPulman@users.noreply.github.com>
What kind of change does this PR introduce?
Update
What is the new behavior?
Apply modern C# updates and various test fixes across the async library. Highlights:
Please check if the PR fulfills these requirements
Other information: