Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1146c96
Move UI and platform types out of System.Reactive
idg10 Feb 12, 2024
af836b7
Ensure Dispatcher available in test that presume it
idg10 Feb 13, 2024
e27c11d
Update verified API
idg10 Feb 13, 2024
8708edf
Add new integration package references to WindowsDesktopTests
idg10 Feb 13, 2024
56c6da7
Add missing s in WindowsForms
idg10 Feb 13, 2024
e28289b
Set version to 7.0 and update WindowsDesktopTests
idg10 Feb 14, 2024
23751e0
Change .Integration package names to .For
idg10 Mar 4, 2024
f9ce5f0
Merge main into feature/separate-ui-packages
idg10 Jun 12, 2025
35c735d
Fixes required now MSBuild.Extras.SDK has gone
idg10 Jun 12, 2025
a1159cd
Turn System.Reactive into facade
idg10 Jun 18, 2025
8df98cb
Hide UI types in facade reference assemblies
idg10 Jul 16, 2025
b7d1ca9
Use ref hiding trick to preserve System.Reactive as main package
idg10 Jul 17, 2025
a39e722
Update preview tag in version.json
idg10 Jul 17, 2025
84ff11f
Remove reference to facades\System.Reactive in test projects
idg10 Jul 17, 2025
d0765bb
Get correct ref assembly used in P2P refs to System.Reactive
idg10 Jul 17, 2025
b47c5c3
Add NuGet readme that got lost
idg10 Jul 17, 2025
c59ebd3
Remove frameworkReference from nuspec
idg10 Jul 18, 2025
d6ba782
Merge main into feature/packaging-no-facade-ref-no-ui
idg10 Sep 3, 2025
7098bd1
Fix integration test version numbers
idg10 Sep 3, 2025
ae84d52
Update ADR to reflect our current view
idg10 Sep 4, 2025
0f5f5b1
Further ADR clarification
idg10 Sep 8, 2025
8037181
Yet more ADR clarifications
idg10 Sep 8, 2025
502280d
Move ref assembly out of Facades folder into FrameworkIntegrations
idg10 Sep 8, 2025
18ff2b3
Remove duplicate PackageReferences
idg10 Sep 8, 2025
f634189
Fix some new analyzer diagnostics
idg10 Sep 8, 2025
b32eee2
Initial analyzer for recommending UI packages
idg10 Sep 9, 2025
ebd30e4
WPF package detection in analyzer
idg10 Sep 9, 2025
1e4b6a1
Basic WPF and Windows Forms missing package analyzer working
idg10 Sep 11, 2025
ed608d6
Add analyzer to System.Reactive package
idg10 Sep 12, 2025
5f6d990
Try to fix apparently spurious license header warnings
idg10 Sep 12, 2025
792156d
Add analyzer checks for UI dispatcher static properties
idg10 Sep 16, 2025
db7e9a5
Add Windows Runtime package verifier checks
idg10 Sep 24, 2025
ed0f980
Merge main into feature/packaging-facade-ref-no-ui
idg10 Oct 6, 2025
276f20b
Merge main into feature/packaging-facade-ref-noui
idg10 Oct 6, 2025
6a10106
Fix typo in UAP build ADR.
idg10 Oct 6, 2025
0773984
Renumber APIs after merge from main added existing 0004
idg10 Oct 6, 2025
934c3a2
Update Windows.winmd ref to 10.0.26100 for Uwp project
idg10 Oct 6, 2025
a16398f
More updates for Windows.winmd moving to 10.0.26100
idg10 Oct 6, 2025
5be94f6
Add v7 release history doc
idg10 Oct 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,10 @@ csharp_space_between_square_brackets = false
dotnet_diagnostic.IDE0290.severity = none

# Namespace style.
csharp_style_namespace_declarations = block_scoped
csharp_style_namespace_declarations = block_scoped
dotnet_diagnostic.IDE0290.severity = none

# Target-typed new expressions
# We will probably adopt these at some point, but for some reason the IDE only just started complaining about them,
# and I don't want to deal with all these while in the middle of the Slight Deunification.
dotnet_diagnostic.IDE0090.severity = none
21 changes: 21 additions & 0 deletions Rx.NET/Documentation/ReleaseHistory/Rx.v7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Rx Release History v7.0

## 7.0.0

New features:

* Applications with a Windows-specific TFM (e.g., `net8.0-windows.10.0.19041`) can now reference the `System.Reactive` package without automatically acquiring a reference to the `Microsoft.Desktop.App` framework (which includes WPF and WinForms). If the application uses either self-contained deployment or AoT, this fixes the problem in which a reference to Rx would massively increase the size of the deployable application
* Tested against .NET 10.0 (in addition to .NET 8 and .NET 9, which Rx 6 already supported)


### Breaking changes

* UI-framework-specific functionality now requires referencing the relevant platform-specific package:
* `System.Reactive.For.WindowsForms` for Windows Forms
* `System.Reactive.For.Wpf` for WPF
* `System.Reactive.For.WindowsRuntime` for WinRT (e.g., `CoreDispatcher`) support
* `System.Reactive.For.Uwp` for UWP support beyond what WinRT offers
* If an application with a Windows-specific had been relying on `System.Reactive` to acquire the `Microsoft.Desktop.App` framework dependency, it will need to add `<UseWPF>true</UseWPF>` or `<UseWindowsForms>true</UseWindowsForms>`
* Out-of-support target frameworks (.NET 6.0, .NET 7.0) no longer supported

Note that the packaging changes for UI-specific functionality is a source-level breaking change, but not a binary-level breaking change. Although the UI-framework-specific types have been removed from the public API of `System.Reactive`, they remain present at runtime. (The NuGet package has both `ref` and `lib` folders. The .NET build tools use the `ref` folder at compile time, and these types have been removed only from the `ref` assembly. At runtime the `lib` folder is used, and the full API of `System.Reactive` v6 remains available in the assemblies in `lib`. Thus existing binaries built against Rx 6.0 that find themselves using Rx 7.0 at runtime will continue to work.)
12 changes: 12 additions & 0 deletions Rx.NET/Documentation/adr/0003-uap-targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ Without these explicit settings, those first two values would have become `UAP,V

However, only _some_ properties should use the old name. We need to set _all_ of these properties, because otherwise, other parts of the build system get confused (e.g., NuGet handling). So we need the ".NETCore" name in some places, and the "UAP" name in others.

Also note that the package validation tooling (which we use to ensure that `System.Reactive` continues to present the same API as it always did despite now being mostly a facade DLL) turns out not to understand the `.NETCore,Version=v5.0` TFM. So for that to work, we need to put the TFM back how it was later in the build process, which is why we have this target:

```xml
<Target Name="_SetUwpTfmForPackageValidation" BeforeTargets="RunPackageValidation">
<ItemGroup>
<PackageValidationReferencePath Condition="%(PackageValidationReferencePath.TargetFrameworkMoniker) == '.NETCore,Version=v5.0'" TargetFrameworkMoniker="UAP,Version=10.0.18362.0" TargetPlatformMoniker="Windows,Version=10.0.18362.0" />
</ItemGroup>
</Target>
```

This also sets the target platform moniker to indicate that this is a Windows-specific TFM, something that the package validation tooling doesn't seem to understand otherwise. (But we do need the target platform identifier to be `UAP` earlier on in the build for various other things to work, which is why we only switch this just before package validation runs.)


#### Compiler Constants

Expand Down
1,341 changes: 1,341 additions & 0 deletions Rx.NET/Documentation/adr/0005-package-split.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# The `System.Reactive` legacy facade contains the UWP-specific `ThreadPoolScheduler`

[`System.Reactive` NuGet package](https://www.nuget.org/packages/System.Reactive/), which used to be the main Rx.NET package, now exists for backwards compatibility. It is mostly a 'facade' containing type forwarders. However, the `uap10.0.18362` target (the target for UWP applications that are _not_ using the .NET runtime support for UWP that was added in .NET 9) includes a `ThreadPoolScheduler` class, and does not use a type forwarder for that type. This document explains why.

## Status

Proposed


## Authors

@idg10 ([Ian Griffiths](https://endjin.com/who-we-are/our-people/ian-griffiths/)).


## Context

As described in [ADR 0005](0005-package-split.md), the [`System.Reactive` NuGet package](https://www.nuget.org/packages/System.Reactive/) is no longer the main Rx.NET package. `System.Reactive.Net` is now the main package, with all UI-framework-specific functionality moved into separate packages. This means applications only get support for a UI framework if they asked for it. This fixes a long-standing problem in which self-contained applications using Rx would get a complete copy of the WPF and Windows Forms frameworks even if they used neither.

The [`System.Reactive`](https://www.nuget.org/packages/System.Reactive/) package still exists of course, but its purpose is now backwards compatibility. It is marked as obsolete, to encourage people to move on to the new `System.Reactive.Net` component (and, if required, to add reference to whichever UI-framework-specific Rx.NET integration components they require).

`System.Reactive` needs to retain the same API surface area as in previous versions, so that when an application using components build for, say, Rx 6.0, ends up using a later version of Rx, those older components will still run. If a library has a reference to `System.Reactive` and uses the `System.Reactive.Linq.Observable` class from that component, the CLR will discover that `System.Reactive` does not define this type, and instead contains a type forwarder entry referring to the type of that name in `System.Reactive.Net`.

The situation is similar for UI-framework-specific types. If a library has a reference to `System.Reactive` and uses the `System.Reactive.Concurrency.ControlScheduler` type, again the CLR will discover the type forwarder in `System.Reactive`. But this time, the forwarder will refer to `System.Reactive.For.WindowsForms`, because that is the new home of this UI-framework-specific type. If an application still references `System.Reactive` (or uses libraries that reference this), it will end up with implicit transitive dependencies on all of the UI-framework-specific packages. This is the direct equivalent to how things were back in Rx 6.0, because `System.Reactive` contained all the UI-framework-specific code. It's just that this fact is now visible in the NuGet package dependency structure. The important change here is that once an application move off `System.Reactive` and onto `System.Reactive.Net` (and once all of its Rx.NET-using dependencies have also done so) it will no longer get UI-frameworkspecific code unless it explicitly asks for it with a suitable package reference.

There's one wrinkle in this: UWP's specialized `ThreadPoolScheduler`.

`ThreadPoolScheduler` should be a UI-framework-independent type. It is available in all Rx.NET targets, including `netstandard2.0` and the no-UI-framework-available `netX.0` targets. (E.g., the `net6.0` target in Rx 6.0,.) So it belongs in `System.Reactive.Net`. The problem is that the `System.Reactive` UWP target (the `uap10.0.18362` TFM) contains a slightly different version of this type than all the other targets. It has:

* Three public constructors
* a default constructor
* a constructor accepting a `WorkItemPriority` argument
* a constructor accepting `WorkItemPriority` and `WorkItemOptions` arguments
* Read-only `Priority` and `Options` properties that report the `WorkItemPriority` and `WorkItemOptions` supplied at construction

It makes these available because it is implemented on top of the Windows Runtime [`Windows.System.Threading.ThreadPool`](https://learn.microsoft.com/en-us/uwp/api/windows.system.threading.threadpool?view=winrt-18362). All the other target use the .NET runtime library's [`System.Threading.ThreadPool`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool). This was unavailable in early versions of UWP, necessitating a different implementation of `ThreadPoolScheduler` on that platform. UWP has supported `netstandard2.0` since Windows 10.0.16299 (aka 1709, aka the 'Windows 10 Fall Creators Update'), released in 2017, so there's no longer an absolute requirement for a UWP-specific `ThreadPoolScheduler`: the `netstandard2.0` Rx.NET implementation now works just fine.

However, by the time UWP did get support for .NET `ThreadPool`, it was not possible to modify the UWP implementation to use it. This is because those additional public members described above can only be offered when using the `Windows.System.Threading.ThreadPool`: the `WorkItemPriority` and `WorkItemOptions` and types are specific to that particular thread pool.

Legacy code written for UWP using Rx 6.0 or older may expect `ThreadPoolScheduler` to offer these members. Therefore it is absolutely necessary for the `ThreadPoolScheduler` obtained through a reference to `System.Reactive` when targetting UWP to provide the UWP-specific implementation.

There are a few ways we could achieve this:

1. Continue to have `System.Reactive.Net` offer a UWP-specific target, and have `System.Reactive` forward to the `ThreadPoolScheduler` in `System.Reactive.Net`
2. Define a `System.Reactive.Concurrency.ThreadPoolScheduler` in `System.Reactive.For.Uwp`, and have `System.Reactive` forward to the `ThreadPoolScheduler` in `System.Reactive.Net`
3.

Note that in options 2 and 3, the `System.Reactive.Net` assembly would define its own `ThreadPoolScheduler`. UWP applications would use the `netstandard2.0` target, so if they reference `System.Reactive.Net` directly, they'll get that `ThreadPoolScheduler`, which will not have


## Decision

We have chosen option 3.

Our view is that it was a mistake to add UWP-specific members to the `ThreadPoolScheduler`. We do not want that to be a feature of Rx.NET in normal use. Furthermore, the whole point of the repackaging, of which this change forms a part, was to remove all UI-framework-specific code from the main Rx.NET package. For these reasons, we reject option 1 above.

Since we consider the incorporation of UWP-specific members into `ThreadPoolScheduler` to be a mistake, we want to deprecate their use. To support the UWP-specific functionality, we define a new `UwpThreadPoolScheduler` in the `System.Reactive.For.Uwp` library, so anyone requiring either the UWP-specific constructors or properties, or who rely on some difference in behaviour between the .NET thread pool used by the `netstandard2.0` `ThreadPoolScheduler` and the `Windows.System.Threading.ThreadPool` can have this, they just need to ask for it explicitly. In order to discourage

But it is necessary to support legacy code. So anything built against `System.Reactive` v6 or earlier that targets UWP must continue to get the



## Consequences

Positive
* Legacy code built against `System.Reactive` continues to run with no change in behaviour
* The main Rx.NET component, `System.Reactive.Net` is completely free from any UI-framework-specific code
* Any developer using the UWP-specific members in the legacy `System.Reactive` component will be told to use the new `UwpThreadPoolScheduler` when they upgrade to a new version of Rx.NET (but can continue to use the old one if they really want to)

Negative:
* Two identically-named types

The only way to avoid that would have been to continue to offer a `uap10.0.18362` target in `System.Reactive.Net`. Since new application development on UWP is strongly discouraged, this would seem like a misstep. Moreover, the continued presence of UWP in our build has caused increasing levels of pain over the year, so we really don't want to offer `uap10.0.18362` targets except in cases where they are absolutely required (e.g. in the UWP-specific component and test suite); we hope to move those out into a completely separate project at some point so that we can finally avoid all the problems the UWP causes for the build process.


What about .NET 9 on UWP?
Loading
Loading