Skip to content

feat(reactive): add ContainerDisposable for CompositeDisposable interop - #169

Merged
ChrisPulman merged 1 commit into
mainfrom
feat/reactive-composite-disposable-interop
Aug 19, 2026
Merged

feat(reactive): add ContainerDisposable for CompositeDisposable interop#169
ChrisPulman merged 1 commit into
mainfrom
feat/reactive-composite-disposable-interop

Conversation

@glennawatson

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature - new public API on ReactiveUI.Primitives.Reactive.

What is the new behavior?

ReactiveUI.Primitives.Reactive ships ContainerDisposable, a disposal group a System.Reactive consumer can use as a CompositeDisposable.

  • ContainerDisposable is a MultipleDisposable that converts implicitly to a CompositeDisposable it owns.
    • Hand it to DisposeWith, to a library that takes a CompositeDisposable, or to your own helper - no conversion at the call site.
    • The conversion is identity-stable, so every conversion of the same container yields the same composite.
    • The composite occupies a slot in the container, so anything registered through it is disposed when the container is. Registering after disposal disposes the registration immediately, matching MultipleDisposable.Add.
    • Clear() and Remove() dispose the composite along with everything else; the next conversion hands back a fresh one, so the container stays usable.
  • DisposeWith gains an overload that takes the container. A container is convertible to CompositeDisposable, so without this overload a call site importing both ReactiveUI.Primitives.Reactive and System.Reactive's fluent disposal helpers has two equally-good candidates and does not compile. Taking the container exactly makes this an identity match, which wins outright.

The intended consumer is ReactiveUI.Reactive, whose WhenActivated(Action<MultipleDisposable>) overloads can hand out a ContainerDisposable instead. That is a follow-up in the ReactiveUI repo and is blocked on this shipping - ReactiveUI consumes Primitives as a package reference.

What is the current behavior?

MultipleDisposable ships in ReactiveUI.Disposables, which deliberately has no System.Reactive dependency, so it cannot name CompositeDisposable and no conversion between them exists. Code that gets a MultipleDisposable from WhenActivated and calls System.Reactive's DisposeWith on it does not compile; the workaround is to import ReactiveUI.Primitives.Reactive for the Primitives DisposeWith instead.

Relates to reactiveui/ReactiveUI#4434.

What might this PR break?

None. The change is additive:

  • ContainerDisposable is new, and the existing DisposeWith(MultipleDisposable) overload is untouched.
  • A container passed to DisposeWith binds to the new overload by identity, so the two overloads coexist whether or not System.Reactive's fluent disposal helpers are also imported.

Two things worth knowing when using it:

  • The container's ICollection<IDisposable> view counts the composite as a single entry. Count, Contains and Remove do not see through it to registrations made on the composite.
  • Conversion allocates the composite lazily, on first use, and only in the .Reactive flavour.

Checklist

  • I have read the Contribute guide
  • Tests have been added or updated (for bug fixes / features)
  • Docs have been added or updated (for bug fixes / features)
  • Changes target the main branch
  • PR title follows Conventional Commits

Additional information

The hand-written files are ContainerDisposable.cs, LinqExtensions.ContainerDisposable.cs, ContainerDisposableTests.cs, the added tests in LinqExtensionsTests.cs, and a README paragraph. The rest of the diff is the same two entries repeated across the per-TFM public API baselines.

LinqExtensionsTests.cs now imports System.Reactive.Disposables.Fluent deliberately: that import is what makes the overload-resolution guard a compile-time test rather than a comment.

- Add ContainerDisposable, a MultipleDisposable that converts implicitly to a
  CompositeDisposable it owns and disposes, so a disposal group flows into APIs
  written against System.Reactive without a hand conversion.
- MultipleDisposable ships in the dependency-free ReactiveUI.Disposables package
  and cannot name CompositeDisposable, so the seam has to live here.
- Add a DisposeWith overload taking the container. Without it a call site that
  imports this namespace and System.Reactive's fluent disposal helpers has two
  equally-good candidates and is ambiguous.
@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.15%. Comparing base (72918bd) to head (14d2f4a).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #169      +/-   ##
==========================================
+ Coverage   98.14%   98.15%   +0.01%     
==========================================
  Files         703      705       +2     
  Lines       21733    21761      +28     
  Branches     2679     2680       +1     
==========================================
+ Hits        21330    21360      +30     
+ Misses        199      197       -2     
  Partials      204      204              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ChrisPulman
ChrisPulman merged commit 0ea94a5 into main Aug 19, 2026
13 checks passed
@ChrisPulman
ChrisPulman deleted the feat/reactive-composite-disposable-interop branch August 19, 2026 07:17
@Nextra

Nextra commented Aug 19, 2026

Copy link
Copy Markdown

The lifetimes are only tied in one direction. If the user loses the ContainerDisposable in an accidental implicit conversion, the resulting CompositeDisposable will not dispose all of the containers content. Is that intentional?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants