Browse Source

Move to .NET SDK 9.0.300 (#2208)

Unit tests now run on net9.0 (and continue to run on net8.0, net472, and UWP)
Removed .net6.0 and .net7.0 targets from test runner because those runtimes are out of support
Removed dependency on MSBuild.SDK.Extras
Ian Griffiths 4 months ago
parent
commit
656f625f1e
44 changed files with 1267 additions and 841 deletions
  1. 190 0
      Rx.NET/Documentation/adr/0003-uap-targets.md
  2. 14 1
      Rx.NET/Source/.editorconfig
  3. 32 2
      Rx.NET/Source/Directory.build.props
  4. 19 2
      Rx.NET/Source/Directory.build.targets
  5. 145 3
      Rx.NET/Source/System.Reactive.sln
  6. 3 0
      Rx.NET/Source/benchmarks/Benchmarks.System.Reactive/Benchmarks.System.Reactive.csproj
  7. 2 2
      Rx.NET/Source/benchmarks/Benchmarks.System.Reactive/Program.cs
  8. 1 1
      Rx.NET/Source/facades/System.Reactive.Core/System.Reactive.Core.csproj
  9. 1 1
      Rx.NET/Source/facades/System.Reactive.Interfaces/System.Reactive.Interfaces.csproj
  10. 1 1
      Rx.NET/Source/facades/System.Reactive.Linq/System.Reactive.Linq.csproj
  11. 1 1
      Rx.NET/Source/facades/System.Reactive.PlatformServices/System.Reactive.PlatformServices.csproj
  12. 1 1
      Rx.NET/Source/facades/System.Reactive.Providers/System.Reactive.Providers.csproj
  13. 1 1
      Rx.NET/Source/facades/System.Reactive.Windows.Threading/System.Reactive.Windows.Threading.csproj
  14. 7 2
      Rx.NET/Source/facades/System.Reactive.WindowsRuntime/System.Reactive.WindowsRuntime.csproj
  15. 0 5
      Rx.NET/Source/global.json
  16. 0 29
      Rx.NET/Source/src/Microsoft.Reactive.Testing/IAssertionException.cs
  17. 2 7
      Rx.NET/Source/src/Microsoft.Reactive.Testing/Microsoft.Reactive.Testing.csproj
  18. 6 1
      Rx.NET/Source/src/System.Reactive.Observable.Aliases/System.Reactive.Observable.Aliases.csproj
  19. 37 6
      Rx.NET/Source/src/System.Reactive/Concurrency/TaskPoolScheduler.cs
  20. 3 3
      Rx.NET/Source/src/System.Reactive/Linq/QueryLanguage.Multiple.cs
  21. 12 4
      Rx.NET/Source/src/System.Reactive/Notification.cs
  22. 5 1
      Rx.NET/Source/src/System.Reactive/System.Reactive.csproj
  23. 3 3
      Rx.NET/Source/tests/Tests.System.Reactive.ApiApprovals/Api/ApiApprovalTests.cs
  24. 1 1
      Rx.NET/Source/tests/Tests.System.Reactive.ApiApprovals/Tests.System.Reactive.ApiApprovals.csproj
  25. 6 3
      Rx.NET/Source/tests/Tests.System.Reactive.Uwp.DeviceRunner/Tests.System.Reactive.Uwp.DeviceRunner.csproj
  26. 2 0
      Rx.NET/Source/tests/Tests.System.Reactive/TaskErrorObservation.cs
  27. 6 6
      Rx.NET/Source/tests/Tests.System.Reactive/Tests.System.Reactive.csproj
  28. 2 0
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Concurrency/ControlSchedulerTest.cs
  29. 3 0
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Concurrency/DispatcherSchedulerTest.cs
  30. 10 4
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Concurrency/TaskPoolSchedulerTest.cs
  31. 1 1
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Disposables/DisposableTests.cs
  32. 347 347
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/CombineLatestTest.cs
  33. 1 1
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/ConcatTest.cs
  34. 6 6
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/DelayTest.cs
  35. 2 0
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/DoTest.cs
  36. 3 3
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/ForTest.cs
  37. 1 1
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/OnErrorResumeNextTest.cs
  38. 1 1
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/SequenceEqualTest.cs
  39. 1 1
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/SingleAsyncTest.cs
  40. 2 0
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/SubscribeTest.cs
  41. 360 360
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/ZipTest.cs
  42. 9 9
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/QbservableTest.cs
  43. 2 0
      Rx.NET/Source/tests/Tests.System.Reactive/Tests/ObserverTest.cs
  44. 15 20
      azure-pipelines.rx.yml

+ 190 - 0
Rx.NET/Documentation/adr/0003-uap-targets.md

@@ -0,0 +1,190 @@
+# Building uap10.0 (UWP) targets without MSBuild.SDK.Extras
+
+Rx.NET can no longer depend [MSBuild.SDK.Extras](https://github.com/novotnyllc/MSBuildSdkExtras), because that project is no longer maintained. This document describes why Rx.NET has been using it, and how we are moving off it.
+
+In short, Rx.NET used `MSBuild.SDK.Extras` to support UWP by offering `uap10.0` targets. Modern .NET projects don't support this TFM directly—new UWP projects still use the old project format that predates .NET Core. As of .NET 9.0, there's no official way to build a multi-target .NET NuGet package that includes a `uap10.0` target. (The 9.0 SDK does now support building for UWP, but that has been enabled by adding support for using .NET 9.0 in UWP. There is still no way to build for the older `uap` TFMs.) For years, we worked around this using the [MSBuild.SDK.Extras](https://github.com/novotnyllc/MSBuildSdkExtras) project, which was written and maintained by someone who also used to be a maintainer on the Rx.NET project. The person no longer works on Rx.NET, and nobody has done any work on `MSBuild.SDK.Extras` for several years.
+
+As we started to prepare for the arrival of .NET 9.0, it became clear that it was time to stop using `MSBuild.SDK.Extras`.
+
+## Status
+
+Accepted
+
+
+## Authors
+
+@idg10 ([Ian Griffiths](https://endjin.com/who-we-are/our-people/ian-griffiths/)).
+
+
+## Context
+
+[UWP](https://en.wikipedia.org/wiki/Universal_Windows_Platform) is an API Microsoft introduced with Windows 10 to support building Windows desktop applications (and, at the time, Windows Phone applications). Microsoft now discourages its used, encouraging developers to use [WinUI](https://learn.microsoft.com/en-us/windows/apps/winui/) instead. However, although UWP is no longer under active development, it continues to be supported. Visual Studio 2022, the current version of Visual Studio at the time of writing this, includes UWP tooling.
+
+Rx.NET has supported UWP applications since v3.0.0 in 2016. When UWP first shipped, it could only use NuGet packages that provided a `uap10.0` TFM. At this time, the core Rx functionality was in `System.Reactive.Core`, and [v3.0.0](https://www.nuget.org/packages/System.Reactive.Core/3.0.0) of that included a `uap10.0` TFM. UI-specific functionality (notably schedulers with support for the UWP dispatcher) was in `System.Reactive.WindowsRuntime`, the same package that had historically supported the Windows 8 era Windows Store apps and also Windows Phone 8 apps. The UWP-specific features were implemented as a new target in that existing package because the dispatcher type in UWP was designed to look very similar to its equivalent in those other APIs. It was possible to write source code that could target all three platforms.
+
+In 2017, [UWP got support for .NET Standard 2.0](https://devblogs.microsoft.com/dotnet/announcing-uwp-support-for-net-standard-2-0/). This appeared in the "Windows 10 Fall Creators Update" also known as version 1709. The corresonding SDK version for this was 10.0.16299. At this point, UWP applications weren't restricted to NuGet packages that offered explicit UWP support. Any NuGet package with a `netstandard2.0` target could be used. However, not everyone upgrades to the latest version of Windows immediately. The preceding version, 1703 ("Creators Update") remained in support until October 2019.
+
+(An even older version of Windows 10, 1607, remains available on the [Long Term Servicing Channel](https://learn.microsoft.com/en-us/windows/release-health/release-information) and is currently slated to remain in support until October 2026. So even as I write this in October 2024, versions of Windows running a version of UWP that do _not_ support .NET Standard 2.0 will remain in support for another 2 years! However, since Rx.NET is not a Microsoft product, we don't attempt to offer that kind of extended life support. People who want to use Rx.NET on such ancient versions are free either to use older versions of Rx.NET, or to fork their own versions.)
+
+So in practice, we needed to continue to offer `uap10.0` targets of the main Rx.NET functionality at least until October 2019. And the components of Rx.NET that offer UWP-specific functionality need to do so through `uap10.0` targets even now, because platform-specific functionality is not available on `netstandard2.0`.
+
+So in October 2019 it would have become reasonable to drop `uap10.0` support for the core of Rx.NET (on the grounds that all versions of Windows in mainstream support were by then perfectly capable of using the `netstandard2.0` version in UWP). By this time, Rx.NET was on v4.2.0, so this was some time after the 'great unification' in which all Rx.NET functionality was merged into a single `System.Reactive` package. Although this solved some problems, it has gone on to cause enormous headaches.
+
+One of the problems caused by the _great unification_ is that although UWP had become capable of using `netstandard2.0` it was no longer possible to remove the `uap10.0` target from the `System.Reactive` package. This is because the UWP-specific features that used to live in `System.Reactive.WindowsRuntime` (e.g., schedulers offering UWP dispatcher support) had been moved into `System.Reactive`. They were only compiled into the `uap10.0` target, but if we removed them, anyone maintaining a UWP app who upgraded to the latest `System.Reactive` would find that their code no longer worked. If they were lucky they'd get a compile time error, but with complex NuGet dependency trees, it's not uncommon to have dependencies on multiple packages each with depdendencies on different versions of Rx.NET without the application developer ever explicitly having chosen to use Rx.NET. If upgrading some other package upgraded them to a new version of `System.Reactive` that no longer offered the `uap10.0` target (meaning they were using the `netstandard2.0` target in a newer version) they might get runtime errors complaining about a missing class emerging from the depths of some library that was using Rx, possibly without the application author ever previously having been aware that they had this transitive dependency on Rx.
+
+So we continued to provide `uap10.0` targets for `System.Reactive` and a few other components.
+
+[MSBuild.SDK.Extras](https://github.com/novotnyllc/MSBuildSdkExtras) made this possible, but it has not been updated since December 2021, meaning that depending on it is problematic.
+
+Microsoft has announced that [.NET 9.0 will finally be getting some support for UWP](https://devblogs.microsoft.com/ifdef-windows/preview-uwp-support-for-dotnet-9-native-aot/), but unfortunately this doesn't help us much. We still need to provide the `uap10.0` target so as not to break existing app developers who don't upgrade to .NET 9.0, and this new tooling doesn't support building for that target. (Instead, it enables using the .NET 9.0 runtime on UWP, meaning that apps built this new way can just use the normal .NET target of Rx.)
+
+So we need to find a way to do something similar to what `MSBuild.SDK.Extras` achieved: i.e. to provide the settings required to convince the .NET SDK to build suitable targets.
+
+## Decision
+
+Firstly, we can't just set `<UseUwpTools>True</UseUwpTools>` to use the new UWP support in the .NET 9.0 SDK because, as stated above, that does not support building for a `uap10.0` TFM.
+
+We will do the following:
+* Remove all use of `MSBuild.SDK.Extras` from all `csproj` files and the `global.json` file
+* Set MSBuild variables as necessary to enable `uap10.0.18362` targets to compile without error
+* Add the required metadata references to enable `uap10.0.18362` targets to compile without error
+
+We will *not* do the following:
+* Attempt to import the full set of `.props` and `.targets` files normally used when building UWP apps
+
+Our goal is _not_ to recreate as faithfully as possible the build environment that C# code would have inside a conventional UWP project. (That _is_ more or less what `MSBuild.SDK.Extras` attempted to do, because it was trying to be generally useful to any project that ran into this problem.) Our goal is to continue to be able to build binaries that work on UWP systems just like they always have. So we need only to do the bare mininum required for that to work. We don't need any XAML support, for example.
+
+To achieve this in practice requires us to solve a few problems. The following sections provide some important background information, and then go on to describe what has been done and why.
+
+### Background
+
+These next sections explain some of the contextual information required to understand the design.
+
+#### TFM and Platform Versions
+
+The precise TFM recent versions have targetted for UWP is `uap10.0.18362`. The version number determines the UWP API version that is available to the code. We moved to this (from `uap10.0.16299`) in March 2023 as part of the work required to get Rx.NET building on Visual Studio 2022. This was a very conservative choice: it was the lowest version we could use that would work with the current tooling. The most recent version of Windows not to support 18362 had already been out of support for several years back when we did this.
+
+With this latest update to move off `MSBuild.SDK.Extras` we don't see any reason to change. Visual Studio 2022 is still the latest version, and it continues to support 10.0.18362 as a target version.
+
+Note that the Windows version in a version-specific TFM doesn't strictly determine the OS minimum version. It determines which OS API version you build against, so in fact it determines how recent a set of OS APIs you can _attempt_ to use, but it is technically possible to write code that attempts to use an OS API but gracefully downgrades behaviour if that API is not present. So it is possible for components to specify a lower `TargetPlatformMinVersion` than the version in their TFM.
+
+The following versions are of interest. The comments about the status of the later versions was correct on 2025/06/05:
+
+* `10.0.16299`: the version that added `netstandard2.0` support to UWP; this would be the lower bar for us were it not for the next item
+* `10.0.17763`: the oldest version to support C#/WinRT, and therefore an absolute lower bar for us (aka 1809, this version is still in extended support)
+* `10.0.18362`: the oldest SDK version supported in Visual Studio 2022
+* `10.0.19041`: the version targeted by Rx's `windows-` TFMs; I never found out why it was this particular version, although it might simply be that this version of Windows (aka 2004, aka 20H1) which shipped in May 2020 was the current version when .NET 5 support was added to Rx.NET
+* `10.0.19045`: Windows 10 22H2, the last ever version of Windows 10 (support ends October 2025)
+* `10.0.22621`: the oldest Windows 11 version (22H2) still in GA support (enterprise only; support ends October 2025)
+* `10.0.22631`: the oldest Windows 11 version (23H2) with GA support for Home, Pro and Education (non-enterprise servicing ends November 2025; enterprise servicing ends November 2026)
+* `10.0.26100`: the latest version of Windows (24H2)
+
+So as it happens, we don't technically need anything newer than 10.0.17763. So we could specify that as the minimum platform version. However, there's no compelling reason to do this, and since 10.0.18362 is as far back as the current tooling fully understands, and is the version Rx 6.0 has always targetted, it makes sense to continue with that.
+
+
+#### Azure DevOps Build Agents
+
+As of 2024/10/24, Azure DevOps Windows build agents have only these Windows SDK versions installed:
+
+* 10.0.17763.0
+* 10.0.19041.0
+* 10.0.20348.0
+* 10.0.22000.0
+* 10.0.22621.0
+
+Note that the specific version we actually want to target, 18362, isn't in there. This is OK because the 19041 SDK is capable of building applications that target 18362, but it does occasionally make for some confusing configuration in the build files. (As I'll describe later, in some places we have to refer to the `10.0.190401.0` folder paths when targeting `10.0.18362`)
+
+
+### Implementation details
+
+The following sections explain how we enable `uap10.0.18362` to be specified as a target framework, even though the tools do not support this.
+
+The project has `Directory.build.props` and `Directory.build.targets` files. The build tools search for these and automatically load them for all projects in the solution. The `Directory.build.props` file has a `<PropertyGroup>` with a `Condition` that means it runs only when the `uap10.0.18362` target is being built, and it sets numerous properties, as described in the following sections.
+
+
+#### Target Platform Version
+
+We make our minmum platform version match the one in the TFM:
+
+```xml   
+<TargetPlatformMinVersion>10.0.18362</TargetPlatformMinVersion>
+<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
+```
+
+Note that it's necessary to specify the `TargetPlatformMinVersion` property as well as the `<TargetPlatformVersion>`. The `Microsoft.NetCore.UniversalWindowsPlatform` NuGet package (which I'll discuss shortly) uses it, and we get a lot of perplexing compiler errors if we don't set this property.
+
+By default the SDK will pull apart the TFM into various other properties that describe its components. However, if we let it do that with a UAP TFM, the build fails. So we set all the relevant properties manually:
+
+```xml
+<TargetFrameworkMoniker>.NETCore,Version=v5.0</TargetFrameworkMoniker>
+<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
+<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
+<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
+<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
+```
+
+Without these explicit settings, those first two values would have become `UAP,Version=v10.0` and `UAP`, but this goes on to cause a cascade of errors because the rest of the SDK does not know what those are. The ".NETCore" v5.0 is not the same thing as .NET 5.0. I believe it corresponds to a short TFM of `netcore50` (whereas .NET 5.0 is `net5.0`). I think this is a hangover from the fact that the predecessors of UWP apps, Windows 8 Store Apps, were actually the first to ship a version of .NET Core. They used .NET Core before .NET Core 1.0 shipped, and confusingly they called it `netcore45` (`netcore451` in Windows 8.1). The list of [deprecated TFMs](https://learn.microsoft.com/en-us/dotnet/standard/frameworks#deprecated-target-frameworks) shows that `netcore50` was an old name for `uap10.0`. But apparently some parts of the SDK did't get that memo, and still require the old name to be used!
+
+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.
+
+
+#### Compiler Constants
+
+When using the supported UWP build tools (with the old-form project system, which we can't use because we also need to build modern targets), the `WINDOWS_UWP` define constant is set, enabling source code compiled into multiple targets to detect that it is being built for UWP with a `#if WINDOWS_UWP`. So we need this in `Directory.build.props`:
+
+```xml
+<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
+```
+
+
+#### Packages and metadata
+
+Normally, when you specify a TFM, the .NET SDK works out what framework library references are required and adds them for you. So if you write `<TargetFramework>net8.0<TargetFramework>` in a project file, you will automatically have access to all the .NET 8.0 runtime libraries. But because the .NET SDK does not support UWP, this doesn't work at all. So we need to do three things.
+
+First, we need to set this property in `Directory.build.props`:
+
+```xml
+<NoStdLib>True</NoStdLib>
+```
+
+Without this, the build tools attempt to add a reference to `mscorlib.dll`, but they don't seem to realise that a) this is the wrong thing and b) they don't actually have a correct location for that, so the reference ends up being `\mscorlib.dll` (i.e., it looks on the root of the hard drive).
+
+Second we need an `ItemGroup` in `Directory.build.targets` containing this:
+
+```xml
+<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"
+                  Version="6.2.12" />
+```
+
+The provides references to the .NET runtime library components. (So this provides the behaviour that you normally get automatically from the SDK, but which we disabled by setting the `NoStdLib` property. We need to do it this way because the SDK doesn't know the right way to do this for UWP projects.)
+
+So this enables normal .NET code to compile. However, Rx.NET also includes code that uses some UWP-specific APIs. (After all, a large part of the issue we're dealing with here exists because of features like schedulers that support UWP dispatchers.) And for that to work, the compiler needs access to `.winmd` files with the metadata for these APIs. So we have this:
+
+```xml
+<ReferencePath Include="$(TargetPlatformSdkPath)UnionMetadata\10.0.19041.0\Windows.winmd" />
+```
+
+This relies on the `TargetPlatformSdkPath` build variable being set. When building locally (either in Visual Studio, or with `dotnet build` from the command line) this variable is set correctly, but for some reason it doesn't seem to be set on the build agents. So we set this as an environment variable in the `azure-pipelines.rx.yml` build pipeline definition.
+
+You might be wondering about that 19041 in there. Why is that not 18362, consistent with the TFM? This is because, as mentioned earlier, Azure DevOps Windows build agents have only certain Windows SDK versions installed. They don't have 18362. but they do have the 19041 version, and we can use that to target `10.0.18362`.
+
+
+#### Prevent Over-Zealous WinRT Interop Code Generation
+
+The .NET SDK has a feature by which it can generate WinRT versions of .NET types to enable interop between .NET and WinRT code. Unfortunately, the way we've rigged things up to be able to build for `uap10.0.18362.0` seems to cause this to generate these interop types for any .NET class that implements `IDisposable`! This is not helpful. So we disable the feature in `Directory.build.targets`:
+
+```xml
+<CsWinRTAotOptimizerEnabled>false</CsWinRTAotOptimizerEnabled>
+```
+
+This shouldn't affect projects using this library. This setting only affects what the tools do while building Rx.NET itself, and as far as we know, we don't have any need for this particular interop feature.
+
+
+#### Implementation limitations
+
+First, note that any project with a UWP has to use the plural `<TargetFrameworks>` MSBuild property. This includes the `System.Reactive.Windows.Runtime` project which targets this and nothing else. Normally you'd use the singular `<TargetFramework>` in that case, but this puts the build into a different mode, and the measures we've taken to enable UWP targets only work in multi-target mode.
+
+
+## Consequences
+
+We no longer have a dependency on an unsupported build extension. We can now build using `dotnet build`. It used to be necessary to use `msbuild` to build from the command line. We now properly support .NET 9.0 because we run tests on it.

+ 14 - 1
Rx.NET/Source/src/.editorconfig → Rx.NET/Source/.editorconfig

@@ -34,7 +34,18 @@
 # reported as unnecessary on ObservableEx, but it is, so we've squelched it.
 # IL2060 is considered unnecessary on targets that don't support trimming, but we don't want to wrap every
 # single one in a conditional, so we also suppress warnings about unnecessary suppressions on those.
-dotnet_remove_unnecessary_suppression_exclusions = CA1704,CA1711,IL2060
+#
+# dotnet_remove_unnecessary_suppression_exclusions = CA1704,CA1711,IL2060
+#
+# Since moving to .NET SDK 9.0, we now get a lot more of these inexplicable IDE0079 warnings, where
+# if we remove the suppression that it complains about, we immediately get the warning that the suppression
+# is there to prevent. As far as I can tell, every suppression now causes a spurious IDE0079, so we
+# just have to disable IDE0079. Presumably there's something odd about this codebase that is confusing
+# the IDE0079 diagnostic - it can't be completely broken for everyone, because someone would have
+# noticed. I guess this may be related to the curse placed on this codebase by The Great Unification,
+# and in particular the corresponding requirement for putting a uap10.0.xxxx TFM on packages that
+# really shouldn't have them.
+dotnet_diagnostic.IDE0079.severity = silent
 
 
 # Prevent IDE1006 (Naming rule violation) errors for non-public fields.
@@ -68,3 +79,5 @@ dotnet_naming_symbols.protected_field_symbols.applicable_accessibilities = prote
 dotnet_naming_rule.protected_instance_fields_must_be_camel_cased_underscore_prefix.symbols  = protected_field_symbols
 dotnet_naming_rule.protected_instance_fields_must_be_camel_cased_underscore_prefix.style    = camel_case_and_prefix_with_underscore_style
 dotnet_naming_rule.protected_instance_fields_must_be_camel_cased_underscore_prefix.severity = none
+
+dotnet_style_require_accessibility_modifiers = for_non_interface_members

+ 32 - 2
Rx.NET/Source/Directory.build.props

@@ -39,7 +39,7 @@
     -->
     <PackageReference
       Include="Nerdbank.GitVersioning"
-      Version="3.6.128"
+      Version="3.6.143"
       PrivateAssets="all"
       Condition="$(ProjectName) != 'Tests.System.Reactive.Uwp.DeviceRunner'" />
   </ItemGroup>
@@ -52,6 +52,27 @@
     <PackageReference Include="coverlet.collector" Version="3.2.0" />
   </ItemGroup>
 
+
+  <PropertyGroup Condition="'$(TargetFramework)'=='uap10.0.18362'">
+    <!--
+    See 0003-uap-targets.md ADR in documentation for the reasons behind these settings.
+    -->
+    <NoStdLib>True</NoStdLib>
+
+    <TargetPlatformMinVersion>10.0.18362</TargetPlatformMinVersion>
+    <TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
+
+    <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
+    <TargetFrameworkMoniker>.NETCore,Version=v5.0</TargetFrameworkMoniker>
+    <TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
+    <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
+    <NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
+
+    <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
+
+  </PropertyGroup>
+
+
   <PropertyGroup>
     <AnalysisLevelDesign>7.0-default</AnalysisLevelDesign>
     <AnalysisLevelNaming>7.0-all</AnalysisLevelNaming>
@@ -72,9 +93,16 @@
                 a chance to shut down. Each of these likely needs individual review:
                 https://github.com/dotnet/reactive/issues/1927
 
+    IDE0028 - Suggests Collection expressions in place of construction, e.g. replaces new List<int>(x)
+              [.. x]. To me, this is less readable, and it doesn't improve performance, as far as we know.
+              Annoyingly, we don't seem to be able to configure this to apply only when it would not
+              introduce a spread.
     IDE0056 - Use of index/range syntax - relevant types not available on all targets, so we can't
     IDE0057     do this.
 
+    IDE0130 - Namespace does not match folder structure. Conforming to this would be a significant
+                upheaval, and it's not clear that it would be an improvement.
+
     IDE0290 - Primary ctors. This diagnostic suggests them in a lot of places where we don't want
                 them. E.g., in most types with multiple constructors I find I prefer not to have
                 a primary ctor. Since this is all or nothing, we turn it off.
@@ -84,11 +112,13 @@
                   [.. readyList]
               This won't improve performance as far as we know (sometimes a reason for using that
               syntax), and it's not obviously an improvement in readability.
+    IDE0306 - Suggests Collection expressions in place of construction, e.g. replaces new List<int>(x)
+              [.. x]. To me, this is less readable, and it doesn't improve performance, as far as we know.
 
     CA1510 - use ArgumentNullException.ThrowIf (not available on all targets)
     CA1513 - use ObjectDisposedException.ThrowIf (not available on all targets)
     -->
-    <NoWarn>$(NoWarn);CA1001;CA2213;CA1510;CA1513;IDE0056;IDE0057;IDE0290;IDE0305</NoWarn>
+    <NoWarn>$(NoWarn);CA1001;CA2213;CA1510;CA1513;IDE0028;IDE0056;IDE0057;IDE0130;IDE0290;IDE0305;IDE0306</NoWarn>
   </PropertyGroup>
 
   <ItemGroup>

+ 19 - 2
Rx.NET/Source/Directory.build.targets

@@ -11,7 +11,6 @@
   </PropertyGroup>
   <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.18362'">
     <DefineConstants>$(DefineConstants);HAS_WINRT;WINDOWS;HAS_OS_XAML;LEGACY_WINRT;NO_NULLABLE_ATTRIBUTES</DefineConstants>
-    <TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
   </PropertyGroup>
   <PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
     <DefineConstants>$(DefineConstants);HAS_WINRT;NO_NULLABLE_ATTRIBUTES</DefineConstants>
@@ -19,7 +18,7 @@
   <PropertyGroup Condition="$(TargetFramework.StartsWith('net6.0')) or $(TargetFramework.StartsWith('net7.0')) or $(TargetFramework.StartsWith('net8.0'))">
     <DefineConstants>$(DefineConstants);HAS_TRIMMABILITY_ATTRIBUTES</DefineConstants>
   </PropertyGroup>
-  <PropertyGroup Condition="$(TargetFramework.StartsWith('net6.0-windows')) or $(TargetFramework.StartsWith('net7.0-windows')) or $(TargetFramework.StartsWith('net8.0-windows'))">
+  <PropertyGroup Condition="$(TargetFramework.StartsWith('net6.0-windows')) or $(TargetFramework.StartsWith('net8.0-windows')) or $(TargetFramework.StartsWith('net9.0-windows'))">
     <DefineConstants>$(DefineConstants);HAS_WINRT;HAS_WINFORMS;HAS_WPF;HAS_DISPATCHER;DESKTOPCLR;WINDOWS;CSWINRT</DefineConstants>
   </PropertyGroup>
 
@@ -27,6 +26,24 @@
     <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
   </ItemGroup>
 
+  <ItemGroup Condition="'$(TargetFramework)'=='uap10.0.18362'">
+    <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"
+                    Version="6.2.14" />
+  </ItemGroup>
+
+
+  <PropertyGroup Condition="$(TargetFramework.StartsWith('net6.0-windows')) or $(TargetFramework.StartsWith('net8.0-windows')) or $(TargetFramework.StartsWith('net9.0-windows'))">
+    <!--
+    In the 9.0.100-rc.2.24474.11 SDK, the build tools attempt to perform code generation for a whole load of our types to support WinRT interop.
+    It's looking like it does this for any public type that implements IDisposable!
+    It's possible that this is a bug in the SDK, but it's also possible that this will be resolved as by design.
+    For the .NET 9.0 RC2 SDK at least, we need to opt out the CsWinRT AOT optimizer.
+    We need to check whether this is definitely OK. I think this is only meant for types that are intended to be used from WinRT,
+    which isn't the case here, but we need to verify this.
+    -->
+    <CsWinRTAotOptimizerEnabled>false</CsWinRTAotOptimizerEnabled>
+  </PropertyGroup>
+
   <Target Name="AddCommitHashToAssemblyAttributes" BeforeTargets="GetAssemblyAttributes">
     <ItemGroup>
       <AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition=" '$(SourceRevisionId)' != '' ">

+ 145 - 3
Rx.NET/Source/System.Reactive.sln

@@ -16,7 +16,7 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{870348D0-C0A0-4352-8A02-E00AB0CCB919}"
 	ProjectSection(SolutionItems) = preProject
 		..\..\.editorconfig = ..\..\.editorconfig
-		src\.editorconfig = src\.editorconfig
+		.editorconfig = .editorconfig
 		analyzers.globalconfig = analyzers.globalconfig
 		..\..\azure-pipelines.rx.yml = ..\..\azure-pipelines.rx.yml
 		Directory.build.props = Directory.build.props
@@ -68,6 +68,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks",
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug (with UWP)|Any CPU = Debug (with UWP)|Any CPU
+		Debug (with UWP)|ARM = Debug (with UWP)|ARM
+		Debug (with UWP)|x64 = Debug (with UWP)|x64
+		Debug (with UWP)|x86 = Debug (with UWP)|x86
 		Debug|Any CPU = Debug|Any CPU
 		Debug|ARM = Debug|ARM
 		Debug|x64 = Debug|x64
@@ -78,6 +82,14 @@ Global
 		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -94,6 +106,14 @@ Global
 		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Release|x64.Build.0 = Release|Any CPU
 		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Release|x86.ActiveCfg = Release|Any CPU
 		{01706A0F-8A63-4FD6-AF45-0BC0BED3C0D9}.Release|x86.Build.0 = Release|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{73078FB5-6038-4674-B4C4-32FD81B88055}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -110,6 +130,14 @@ Global
 		{73078FB5-6038-4674-B4C4-32FD81B88055}.Release|x64.Build.0 = Release|Any CPU
 		{73078FB5-6038-4674-B4C4-32FD81B88055}.Release|x86.ActiveCfg = Release|Any CPU
 		{73078FB5-6038-4674-B4C4-32FD81B88055}.Release|x86.Build.0 = Release|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E072D663-A241-4FEC-B888-45640F69D9CE}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -126,6 +154,14 @@ Global
 		{E072D663-A241-4FEC-B888-45640F69D9CE}.Release|x64.Build.0 = Release|Any CPU
 		{E072D663-A241-4FEC-B888-45640F69D9CE}.Release|x86.ActiveCfg = Release|Any CPU
 		{E072D663-A241-4FEC-B888-45640F69D9CE}.Release|x86.Build.0 = Release|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -142,9 +178,19 @@ Global
 		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Release|x64.Build.0 = Release|Any CPU
 		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Release|x86.ActiveCfg = Release|Any CPU
 		{CB5E4FFA-F510-43F1-B378-B3D7BA7C8396}.Release|x86.Build.0 = Release|Any CPU
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|x86
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|Any CPU.Build.0 = Debug|x86
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|Any CPU.Deploy.0 = Debug|x86
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|ARM.ActiveCfg = Debug|ARM
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|ARM.Build.0 = Debug|ARM
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|ARM.Deploy.0 = Debug|ARM
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|x64.ActiveCfg = Debug|x64
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|x64.Build.0 = Debug|x64
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|x64.Deploy.0 = Debug|x64
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|x86.ActiveCfg = Debug|x86
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|x86.Build.0 = Debug|x86
+		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug (with UWP)|x86.Deploy.0 = Debug|x86
 		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug|Any CPU.ActiveCfg = Debug|x86
-		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug|Any CPU.Build.0 = Debug|x86
-		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug|Any CPU.Deploy.0 = Debug|x86
 		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug|ARM.ActiveCfg = Debug|ARM
 		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug|ARM.Build.0 = Debug|ARM
 		{10CC7191-D936-46CA-BA87-0646733571EA}.Debug|ARM.Deploy.0 = Debug|ARM
@@ -164,6 +210,14 @@ Global
 		{10CC7191-D936-46CA-BA87-0646733571EA}.Release|x86.ActiveCfg = Release|x86
 		{10CC7191-D936-46CA-BA87-0646733571EA}.Release|x86.Build.0 = Release|x86
 		{10CC7191-D936-46CA-BA87-0646733571EA}.Release|x86.Deploy.0 = Release|x86
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{296C5140-7945-439D-B090-AB6250DEEE51}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -180,6 +234,14 @@ Global
 		{296C5140-7945-439D-B090-AB6250DEEE51}.Release|x64.Build.0 = Release|Any CPU
 		{296C5140-7945-439D-B090-AB6250DEEE51}.Release|x86.ActiveCfg = Release|Any CPU
 		{296C5140-7945-439D-B090-AB6250DEEE51}.Release|x86.Build.0 = Release|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -196,6 +258,14 @@ Global
 		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Release|x64.Build.0 = Release|Any CPU
 		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Release|x86.ActiveCfg = Release|Any CPU
 		{3EEB4F40-002A-4F72-9DDF-8E6FE3847B82}.Release|x86.Build.0 = Release|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -212,6 +282,14 @@ Global
 		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Release|x64.Build.0 = Release|Any CPU
 		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Release|x86.ActiveCfg = Release|Any CPU
 		{68B8D2CD-BB8F-4033-90B0-C282304C2B2B}.Release|x86.Build.0 = Release|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -228,6 +306,14 @@ Global
 		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Release|x64.Build.0 = Release|Any CPU
 		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Release|x86.ActiveCfg = Release|Any CPU
 		{BA291C8D-1ECB-4C87-9C50-10474F3A15A5}.Release|x86.Build.0 = Release|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{79C55CAE-E1B8-4808-9970-73D893627B99}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -244,6 +330,14 @@ Global
 		{79C55CAE-E1B8-4808-9970-73D893627B99}.Release|x64.Build.0 = Release|Any CPU
 		{79C55CAE-E1B8-4808-9970-73D893627B99}.Release|x86.ActiveCfg = Release|Any CPU
 		{79C55CAE-E1B8-4808-9970-73D893627B99}.Release|x86.Build.0 = Release|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{D2303705-01E6-404E-9034-F9CCE502DF00}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -260,6 +354,14 @@ Global
 		{D2303705-01E6-404E-9034-F9CCE502DF00}.Release|x64.Build.0 = Release|Any CPU
 		{D2303705-01E6-404E-9034-F9CCE502DF00}.Release|x86.ActiveCfg = Release|Any CPU
 		{D2303705-01E6-404E-9034-F9CCE502DF00}.Release|x86.Build.0 = Release|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -276,6 +378,14 @@ Global
 		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Release|x64.Build.0 = Release|Any CPU
 		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Release|x86.ActiveCfg = Release|Any CPU
 		{15585B53-9F85-4439-9D80-D827F1AD91E6}.Release|x86.Build.0 = Release|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -292,6 +402,14 @@ Global
 		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Release|x64.Build.0 = Release|Any CPU
 		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Release|x86.ActiveCfg = Release|Any CPU
 		{4DCC120A-FD19-409B-A0E6-A049C4D0B663}.Release|x86.Build.0 = Release|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -308,6 +426,14 @@ Global
 		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Release|x64.Build.0 = Release|Any CPU
 		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Release|x86.ActiveCfg = Release|Any CPU
 		{E9BB350B-D4EF-42E1-B4E2-14058AC6809B}.Release|x86.Build.0 = Release|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -324,6 +450,14 @@ Global
 		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Release|x64.Build.0 = Release|Any CPU
 		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Release|x86.ActiveCfg = Release|Any CPU
 		{A31DDC7E-E883-4DBD-8FB8-D7DFC56059F6}.Release|x86.Build.0 = Release|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
 		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -340,6 +474,14 @@ Global
 		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Release|x64.Build.0 = Release|Any CPU
 		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Release|x86.ActiveCfg = Release|Any CPU
 		{01CCDA6D-4D00-4DF2-82B0-359FD5E0CDC6}.Release|x86.Build.0 = Release|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|Any CPU.ActiveCfg = Current Sources|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|Any CPU.Build.0 = Current Sources|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|ARM.ActiveCfg = Rx.net 4.0|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|ARM.Build.0 = Rx.net 4.0|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|x64.ActiveCfg = Rx.net 4.0|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|x64.Build.0 = Rx.net 4.0|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|x86.ActiveCfg = Rx.net 4.0|Any CPU
+		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug (with UWP)|x86.Build.0 = Rx.net 4.0|Any CPU
 		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug|Any CPU.ActiveCfg = Current Sources|Any CPU
 		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug|Any CPU.Build.0 = Current Sources|Any CPU
 		{5C7906F6-232E-455C-9269-68EF84F393C9}.Debug|ARM.ActiveCfg = Rx.net 4.0|Any CPU

+ 3 - 0
Rx.NET/Source/benchmarks/Benchmarks.System.Reactive/Benchmarks.System.Reactive.csproj

@@ -38,6 +38,9 @@
 
   <ItemGroup>
     <PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
+    <PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
+    <PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
     <PackageReference Include="WindowsBase" Version="4.6.1055" />
   </ItemGroup>
 

+ 2 - 2
Rx.NET/Source/benchmarks/Benchmarks.System.Reactive/Program.cs

@@ -14,7 +14,7 @@ namespace Benchmarks.System.Reactive
         {
             Console.WriteLine("Effective Rx-version: " + typeof(Observable).Assembly.GetName().Version);
 
-            var switcher = new BenchmarkSwitcher(new[] {
+            var switcher = new BenchmarkSwitcher([
                 typeof(ZipBenchmark),
                 typeof(CombineLatestBenchmark),
                 typeof(SwitchBenchmark),
@@ -33,7 +33,7 @@ namespace Benchmarks.System.Reactive
                 ,typeof(AppendPrependBenchmark)
                 ,typeof(PrependVsStartWtihBenchmark)
 #endif
-            });
+            ]);
 
             switcher.Run();
             Console.ReadLine();

+ 1 - 1
Rx.NET/Source/facades/System.Reactive.Core/System.Reactive.Core.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.0;uap10.0.18362</TargetFrameworks>

+ 1 - 1
Rx.NET/Source/facades/System.Reactive.Interfaces/System.Reactive.Interfaces.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.0;uap10.0.18362</TargetFrameworks>

+ 1 - 1
Rx.NET/Source/facades/System.Reactive.Linq/System.Reactive.Linq.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.0;uap10.0.18362</TargetFrameworks>

+ 1 - 1
Rx.NET/Source/facades/System.Reactive.PlatformServices/System.Reactive.PlatformServices.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.0;uap10.0.18362</TargetFrameworks>

+ 1 - 1
Rx.NET/Source/facades/System.Reactive.Providers/System.Reactive.Providers.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.0;uap10.0.18362</TargetFrameworks>

+ 1 - 1
Rx.NET/Source/facades/System.Reactive.Windows.Threading/System.Reactive.Windows.Threading.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFrameworks>net472;uap10.0.18362</TargetFrameworks>

+ 7 - 2
Rx.NET/Source/facades/System.Reactive.WindowsRuntime/System.Reactive.WindowsRuntime.csproj

@@ -1,7 +1,12 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>uap10.0.18362</TargetFramework>
+    <!--
+    Note: this targets just one framework, we need to use the TargetFrameworks property because
+    the way we enable uap TFMs despite the .NET SDK not fully supporting only works when the build
+    is in multi-target mode. See ../../../Documentation/adr/0003-uap-targets.md for details.
+    -->
+    <TargetFrameworks>uap10.0.18362</TargetFrameworks>
   </PropertyGroup>
 
   <PropertyGroup>

+ 0 - 5
Rx.NET/Source/global.json

@@ -1,5 +0,0 @@
-{
-  "msbuild-sdks": {
-    "MSBuild.Sdk.Extras": "3.0.44"
-  }
-}

+ 0 - 29
Rx.NET/Source/src/Microsoft.Reactive.Testing/IAssertionException.cs

@@ -1,29 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT License.
-// See the LICENSE file in the project root for more information. 
-
-namespace Xunit.Sdk
-{
-    /// <summary>
-    /// Marker interface required by xUnit.
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// The <c>xunit.assert.source</c> package normally includes this. However, unlike all the
-    /// other types that package adds to our project, this one type is declared to be
-    /// unconditionally public.
-    /// </para>
-    /// <para>
-    /// We think this might be a bug: https://github.com/xunit/xunit/issues/2703
-    /// </para>
-    /// <para>
-    /// In any case, we do not want our library to be exporting public types in the Xunit.Sdk
-    /// namespace. So the csproj file carefully excludes the Asserts/Sdk/IAssertionException.cs
-    /// file supplied by the package (which defines this type as <c>public</c>), and this file
-    /// supplies an equivalent but <c>internal</c> definition.
-    /// </para>
-    /// </remarks>
-    internal interface IAssertionException
-    {
-    }
-}

+ 2 - 7
Rx.NET/Source/src/Microsoft.Reactive.Testing/Microsoft.Reactive.Testing.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.0;net472;uap10.0.18362;net6.0</TargetFrameworks>
     <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
@@ -18,16 +18,11 @@
 
   <ItemGroup>
     <EmbeddedResource Include="Properties\*.xml" />
-    <PackageReference Include="xunit.assert.source" Version="2.8.0" PrivateAssets="All" /> 
+    <PackageReference Include="xunit.assert.source" Version="2.8.1" PrivateAssets="All" /> 
     <ProjectReference Include="..\System.Reactive\System.Reactive.csproj" />
   </ItemGroup>
 
   <ItemGroup>
     <None Include="build\NuGet.Readme.md" Pack="true" PackagePath="\readme.md" />
-
-    <!--
-    Workaround for https://github.com/xunit/xunit/issues/2703
-    -->
-    <Compile Remove="$(NuGetPackageRoot)xunit.assert.source\**\contentFiles\cs\**\Asserts\Sdk\IAssertionException.cs" />
   </ItemGroup>
 </Project>

+ 6 - 1
Rx.NET/Source/src/System.Reactive.Observable.Aliases/System.Reactive.Observable.Aliases.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.0;net472;uap10.0.18362;net6.0</TargetFrameworks>
     <Title>Reactive Extensions - Aliases</Title>    
@@ -17,4 +17,9 @@
   <ItemGroup>
     <None Include="build\NuGet.Readme.md" Pack="true" PackagePath="\readme.md"/>
   </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFramework)'=='uap10.0.18362'">
+    <ReferencePath Include="$(TargetPlatformSdkPath)UnionMetadata\10.0.19041.0\Windows.winmd" />
+  </ItemGroup>
+
 </Project>

+ 37 - 6
Rx.NET/Source/src/System.Reactive/Concurrency/TaskPoolScheduler.cs

@@ -89,17 +89,48 @@ namespace System.Reactive.Concurrency
                 var ct = new CancellationDisposable();
                 _cancel.Disposable = ct;
 
-                TaskHelpers.Delay(dueTime, ct.Token).ContinueWith(
-                    (_, thisObject) =>
+                static void RunWork(object? thisObject)
+                {
+                    var @this = (SlowlyScheduledWorkItem<TState>)thisObject!;
+
+                    if (!@this._cancel.IsDisposed)
                     {
-                        var @this = (SlowlyScheduledWorkItem<TState>)thisObject!;
+                        @this._cancel.Disposable = @this._action(@this._scheduler, @this._state);
+                    }
+                };
 
-                        if (!@this._cancel.IsDisposed)
+                TaskHelpers.Delay(dueTime, ct.Token).ContinueWith(
+                    static (_, args) =>
+                    {
+                        (var thisObject, var scheduler, var launchingThreadId) =
+                            ((SlowlyScheduledWorkItem<TState>, TaskScheduler, int))args!;
+                        if (Environment.CurrentManagedThreadId == launchingThreadId)
+                        {
+                            // Our request to run this continuation sychronously might have worked slightly too well:
+                            // we are on the same thread that called ContinueWith. This usually indicates that
+                            // the Delay completed before we added the continuation, so we're running inside
+                            // the call to ContinueWith. This can happen from time to time when a very short
+                            // delay has been requested. (This only seems to have started happening with .NET 9.0.)
+                            // Code may be relying on us not invoking work items inside the call to
+                            // IScheduler.Schedule - often work is delivered via the scheduler to avoid stack
+                            // overflows, so we now to run this asynchronously.
+                            // Note that it's possible that we're not in fact in this situation: it's possible
+                            // that we are being invoked asynchronously, and simply happen to be on the same thread
+                            // that initially queued the work. In that case, this asynchronous scheduling of the
+                            // work adds some unnecessary overhead, but won't cause harm.
+                            // (A simpler fix would be to remove the TaskContinuationOptions.ExecuteSynchronously
+                            // below, but that would increase overhead in the common case: the task completion
+                            // login in the CLR would _always_ end up scheduling a new task for us. This more
+                            // complex logic avoids that overhead in most cases.)
+                            var t = new Task(RunWork, thisObject);
+                            t.Start(scheduler);
+                        }
+                        else
                         {
-                            @this._cancel.Disposable = @this._action(@this._scheduler, @this._state);
+                            RunWork(thisObject);
                         }
                     },
-                    this,
+                    (this, scheduler._taskFactory.Scheduler ?? TaskScheduler.Default, Environment.CurrentManagedThreadId),
                     CancellationToken.None,
                     TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
                     scheduler._taskFactory.Scheduler ?? TaskScheduler.Default);

+ 3 - 3
Rx.NET/Source/src/System.Reactive/Linq/QueryLanguage.Multiple.cs

@@ -61,7 +61,7 @@ namespace System.Reactive.Linq
 
         public virtual IObservable<TSource> Catch<TSource>(IObservable<TSource> first, IObservable<TSource> second)
         {
-            return Catch_(new[] { first, second });
+            return Catch_((IObservable<TSource>[])[first, second]);
         }
 
         public virtual IObservable<TSource> Catch<TSource>(params IObservable<TSource>[] sources)
@@ -114,7 +114,7 @@ namespace System.Reactive.Linq
 
         public virtual IObservable<TSource> Concat<TSource>(IObservable<TSource> first, IObservable<TSource> second)
         {
-            return Concat_(new[] { first, second });
+            return Concat_((IObservable<TSource>[])[first, second]);
         }
 
         public virtual IObservable<TSource> Concat<TSource>(params IObservable<TSource>[] sources)
@@ -222,7 +222,7 @@ namespace System.Reactive.Linq
 
         public virtual IObservable<TSource> OnErrorResumeNext<TSource>(IObservable<TSource> first, IObservable<TSource> second)
         {
-            return OnErrorResumeNext_(new[] { first, second });
+            return OnErrorResumeNext_((IObservable<TSource>[])[first, second]);
         }
 
         public virtual IObservable<TSource> OnErrorResumeNext<TSource>(params IObservable<TSource>[] sources)

+ 12 - 4
Rx.NET/Source/src/System.Reactive/Notification.cs

@@ -7,8 +7,16 @@ using System.Diagnostics;
 using System.Globalization;
 using System.Reactive.Concurrency;
 
-#pragma warning disable 0659
-#pragma warning disable 0661
+// Notification<T> defines Equals, ==, and !=, but not GetHashCode.
+// This seems like an oversight, but changing this would technically be a breaking change,
+// so we suprpress the warnings.
+#pragma warning disable CS0659
+#pragma warning disable CS0661
+
+#if LEGACY_WINRT
+#pragma warning disable CA1724 // Name conflicts with Windows.Phone.Devices.Notification
+#endif
+
 
 namespace System.Reactive
 {
@@ -710,5 +718,5 @@ namespace System.Reactive
     }
 }
 
-#pragma warning restore 0659
-#pragma warning restore 0661
+#pragma warning restore CS0659
+#pragma warning restore CS0661

+ 5 - 1
Rx.NET/Source/src/System.Reactive/System.Reactive.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.0;net472;uap10.0.18362;net6.0;net6.0-windows10.0.19041</TargetFrameworks>
     <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
@@ -20,6 +20,10 @@
     <UseWindowsForms>true</UseWindowsForms>
   </PropertyGroup>
 
+  <ItemGroup Condition="'$(TargetFramework)'=='uap10.0.18362'">
+    <ReferencePath Include="$(TargetPlatformSdkPath)UnionMetadata\10.0.19041.0\Windows.winmd" />
+
+  </ItemGroup>
 
   <ItemGroup>
     <Compile Remove="Platforms\**\*.*" />

+ 3 - 3
Rx.NET/Source/tests/Tests.System.Reactive.ApiApprovals/Api/ApiApprovalTests.cs

@@ -57,10 +57,10 @@ namespace ReactiveTests.Tests.Api
 
         private static string Filter(string text)
         {
-            return string.Join(Environment.NewLine, text.Split(new[]
-                                                        {
+            return string.Join(Environment.NewLine, text.Split(
+                                                        [
                                                             Environment.NewLine
-                                                        }, StringSplitOptions.RemoveEmptyEntries)
+                                                        ], StringSplitOptions.RemoveEmptyEntries)
                                                         .Where(l => !l.StartsWith("[assembly: AssemblyVersion("))
                                                         .Where(l => !l.StartsWith("[assembly: AssemblyFileVersion("))
                                                         .Where(l => !l.StartsWith("[assembly: AssemblyInformationalVersion("))

+ 1 - 1
Rx.NET/Source/tests/Tests.System.Reactive.ApiApprovals/Tests.System.Reactive.ApiApprovals.csproj

@@ -25,7 +25,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
     <PackageReference Include="Verify.Xunit" Version="20.4.0" />
     <PackageReference Include="xunit" Version="2.8.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">

+ 6 - 3
Rx.NET/Source/tests/Tests.System.Reactive.Uwp.DeviceRunner/Tests.System.Reactive.Uwp.DeviceRunner.csproj

@@ -139,9 +139,12 @@
     (It's not entirely clear where they get that from, but I think it's to do with the
     Windows API version they select, and I don't want to upgrade those to a newer version.)
     -->
-    <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.12" />
-    <PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
-    <PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
+    <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
+    <PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
+    <PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
+    <PackageReference Include="Newtonsoft.Json">
+      <Version>13.0.3</Version>
+    </PackageReference>
     <PackageReference Include="xunit.assert" Version="2.8.0" />
   </ItemGroup>
   <ItemGroup>

+ 2 - 0
Rx.NET/Source/tests/Tests.System.Reactive/TaskErrorObservation.cs

@@ -71,6 +71,7 @@ namespace Tests.System.Reactive
         public IDisposable SuscribeWithoutKeepingSourceReachable<T>(
             Func<Func<Task<T>, Task<T>>, Exception, IDisposable> subscribe)
         {
+#pragma warning disable IDE0350 // Use implicitly typed lambda - we want to be explicit here for clarity
             return SuscribeWithoutKeepingSourceReachable(
                 (Func<Task, Task> setTask, Exception ex) => subscribe(
                     t =>
@@ -78,6 +79,7 @@ namespace Tests.System.Reactive
                         setTask(t);
                         return t;
                     }, ex));
+#pragma warning restore IDE0350
         }
 
 

+ 6 - 6
Rx.NET/Source/tests/Tests.System.Reactive/Tests.System.Reactive.csproj

@@ -1,11 +1,11 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net472;net6.0;net7.0;net6.0-windows10.0.19041;net8.0;net8.0-windows10.0.19041</TargetFrameworks>
+    <TargetFrameworks>net472;net8.0;net8.0-windows10.0.19041;net9.0;net9.0-windows10.0.19041</TargetFrameworks>
     <NoWarn>$(NoWarn);CS0618</NoWarn>
   </PropertyGroup>
 
-  <PropertyGroup Condition="$(TargetFramework.StartsWith('net6.0-windows')) or '$(TargetFramework)' == 'net472'">
+  <PropertyGroup Condition="$(TargetFramework.StartsWith('net8.0-windows')) or $(TargetFramework.StartsWith('net9.0-windows')) or '$(TargetFramework)' == 'net472'">
     <UseWPF>true</UseWPF>
     <UseWindowsForms>true</UseWindowsForms>
   </PropertyGroup>
@@ -28,9 +28,9 @@
     Before that PR was merged, the test runner wouldn't work if the host specified a TFM with a
     specific Windows version.
 -->
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
-    <PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
-    <PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
+    <PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
+    <PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
     <PackageReference Include="xunit.assert" Version="2.8.0" />
     <ProjectReference Include="..\..\src\System.Reactive\System.Reactive.csproj" />
     <ProjectReference Include="..\..\src\System.Reactive.Observable.Aliases\System.Reactive.Observable.Aliases.csproj" />

+ 2 - 0
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Concurrency/ControlSchedulerTest.cs

@@ -4,6 +4,8 @@
 
 #if HAS_WINFORMS
 
+#pragma warning disable IDE0034 // (Simplify 'default'.) Want to be explicit about overload being tested.
+
 using System;
 using System.Reactive.Concurrency;
 using System.Reactive.Disposables;

+ 3 - 0
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Concurrency/DispatcherSchedulerTest.cs

@@ -3,6 +3,9 @@
 // See the LICENSE file in the project root for more information. 
 
 #if HAS_WPF
+
+#pragma warning disable IDE0034 // (Simplify 'default'.) Want to be explicit about overload being tested.
+
 using System;
 using System.Diagnostics;
 using System.Reactive.Concurrency;

+ 10 - 4
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Concurrency/TaskPoolSchedulerTest.cs

@@ -39,30 +39,36 @@ namespace ReactiveTests.Tests
         public void TaskPool_ScheduleAction()
         {
             var id = Environment.CurrentManagedThreadId;
+            var taskTid = 0;
             var nt = TaskPoolScheduler.Default;
             var evt = new ManualResetEvent(false);
-            nt.Schedule(() => { Assert.NotEqual(id, Environment.CurrentManagedThreadId); evt.Set(); });
+            nt.Schedule(() => { taskTid = Environment.CurrentManagedThreadId; evt.Set(); });
             evt.WaitOne();
+            Assert.NotEqual(id, taskTid);
         }
 
         [TestMethod]
         public void TaskPool_ScheduleActionDueNow()
         {
             var id = Environment.CurrentManagedThreadId;
+            var taskTid = 0;
             var nt = TaskPoolScheduler.Default;
             var evt = new ManualResetEvent(false);
-            nt.Schedule(TimeSpan.Zero, () => { Assert.NotEqual(id, Environment.CurrentManagedThreadId); evt.Set(); });
+            nt.Schedule(TimeSpan.Zero, () => { taskTid = Environment.CurrentManagedThreadId; evt.Set(); });
             evt.WaitOne();
+            Assert.NotEqual(id, taskTid);
         }
 
         [TestMethod]
         public void TaskPool_ScheduleActionDue()
         {
             var id = Environment.CurrentManagedThreadId;
+            var taskTid = 0;
             var nt = TaskPoolScheduler.Default;
             var evt = new ManualResetEvent(false);
-            nt.Schedule(TimeSpan.FromMilliseconds(1), () => { Assert.NotEqual(id, Environment.CurrentManagedThreadId); evt.Set(); });
+            nt.Schedule(TimeSpan.FromMilliseconds(1), () => { taskTid = Environment.CurrentManagedThreadId; evt.Set(); });
             evt.WaitOne();
+            Assert.NotEqual(id, taskTid);
         }
 
         [TestMethod]
@@ -71,7 +77,7 @@ namespace ReactiveTests.Tests
             var id = Environment.CurrentManagedThreadId;
             var nt = TaskPoolScheduler.Default;
             var set = false;
-            var d = nt.Schedule(TimeSpan.FromSeconds(0.2), () => { Assert.True(false); set = true; });
+            var d = nt.Schedule(TimeSpan.FromSeconds(0.2), () => { set = true; });
             d.Dispose();
             Thread.Sleep(400);
             Assert.False(set);

+ 1 - 1
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Disposables/DisposableTests.cs

@@ -29,7 +29,7 @@ namespace ReactiveTests.Tests
         [TestMethod]
         public void AnonymousDisposable_CreateNull()
         {
-            Assert.Throws(typeof(ArgumentNullException), () => Disposable.Create(null));
+            Assert.Throws<ArgumentNullException>(() => Disposable.Create(null));
         }
 
         [TestMethod]

File diff suppressed because it is too large
+ 347 - 347
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/CombineLatestTest.cs


+ 1 - 1
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/ConcatTest.cs

@@ -117,7 +117,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                Observable.Concat(new[] { xs1, xs2, xs3 })
+                Observable.Concat([xs1, xs2, xs3])
             );
 
             res.Messages.AssertEqual(

+ 6 - 6
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/DelayTest.cs

@@ -761,7 +761,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                xs.Delay(x => scheduler.CreateColdObservable(new[] { OnNext(x, "!") }))
+                xs.Delay(x => scheduler.CreateColdObservable([OnNext(x, "!")]))
             );
 
             res.Messages.AssertEqual(
@@ -1515,7 +1515,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                xs.Delay(ys, x => scheduler.CreateColdObservable(new[] { OnNext(x, "!") }))
+                xs.Delay(ys, x => scheduler.CreateColdObservable([OnNext(x, "!")]))
             );
 
             res.Messages.AssertEqual(
@@ -1555,7 +1555,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                xs.Delay(ys, x => scheduler.CreateColdObservable(new[] { OnNext(x, "!") }))
+                xs.Delay(ys, x => scheduler.CreateColdObservable([OnNext(x, "!")]))
             );
 
             res.Messages.AssertEqual(
@@ -1595,7 +1595,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                xs.Delay(ys, x => scheduler.CreateColdObservable(new[] { OnNext(x, "!") })),
+                xs.Delay(ys, x => scheduler.CreateColdObservable([OnNext(x, "!")])),
                 255
             );
 
@@ -1632,7 +1632,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                xs.Delay(ys, x => scheduler.CreateColdObservable(new[] { OnNext(x, "!") })),
+                xs.Delay(ys, x => scheduler.CreateColdObservable([OnNext(x, "!")])),
                 255
             );
 
@@ -1669,7 +1669,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                xs.Delay(ys, x => scheduler.CreateColdObservable(new[] { OnNext(x, "!") }))
+                xs.Delay(ys, x => scheduler.CreateColdObservable([OnNext(x, "!")]))
             );
 
             res.Messages.AssertEqual(

+ 2 - 0
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/DoTest.cs

@@ -27,12 +27,14 @@ namespace ReactiveTests.Tests
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(someObservable, null, () => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do<int>(null, x => { }, () => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(someObservable, x => { }, (Action<Exception>)null));
+#pragma warning disable IDE0350 // Use implicitly typed lambda - we want to be explicit here for clarity
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(someObservable, null, (Exception _) => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do<int>(null, x => { }, (Exception _) => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(someObservable, x => { }, (Exception _) => { }, null));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(someObservable, x => { }, null, () => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(someObservable, null, (Exception _) => { }, () => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do<int>(null, x => { }, (Exception _) => { }, () => { }));
+#pragma warning restore IDE0350
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(null, Observer.Create<int>(i => { })));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.Do(someObservable, default(IObserver<int>)));
         }

+ 3 - 3
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/ForTest.cs

@@ -27,7 +27,7 @@ namespace ReactiveTests.Tests
         {
             var scheduler = new TestScheduler();
 
-            var results = scheduler.Start(() => Observable.For(new[] { 1, 2, 3 }, x => scheduler.CreateColdObservable(
+            var results = scheduler.Start(() => Observable.For([1, 2, 3], x => scheduler.CreateColdObservable(
                 OnNext((ushort)(x * 100 + 10), x * 10 + 1),
                 OnNext((ushort)(x * 100 + 20), x * 10 + 2),
                 OnNext((ushort)(x * 100 + 30), x * 10 + 3),
@@ -89,7 +89,7 @@ namespace ReactiveTests.Tests
 
             var ex = new Exception();
 
-            var results = scheduler.Start(() => Observable.For(new[] { 1, 2, 3 }, x => Observable.Throw<int>(ex)));
+            var results = scheduler.Start(() => Observable.For([1, 2, 3], x => Observable.Throw<int>(ex)));
 
             results.Messages.AssertEqual(
                 OnError<int>(200, ex)
@@ -103,7 +103,7 @@ namespace ReactiveTests.Tests
 
             var ex = new Exception();
 
-            var results = scheduler.Start(() => Observable.For(new[] { 1, 2, 3 }, x => Throw<IObservable<int>>(ex)));
+            var results = scheduler.Start(() => Observable.For([1, 2, 3], x => Throw<IObservable<int>>(ex)));
 
             results.Messages.AssertEqual(
                 OnError<int>(200, ex)

+ 1 - 1
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/OnErrorResumeNextTest.cs

@@ -78,7 +78,7 @@ namespace ReactiveTests.Tests
             );
 
             var res = scheduler.Start(() =>
-                Observable.OnErrorResumeNext(new[] { xs1, xs2, xs3 })
+                Observable.OnErrorResumeNext([xs1, xs2, xs3])
             );
 
             res.Messages.AssertEqual(

+ 1 - 1
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/SequenceEqualTest.cs

@@ -25,7 +25,7 @@ namespace ReactiveTests.Tests
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.SequenceEqual(DummyObservable<int>.Instance, default(IObservable<int>), EqualityComparer<int>.Default));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.SequenceEqual(DummyObservable<int>.Instance, DummyObservable<int>.Instance, default));
 
-            ReactiveAssert.Throws<ArgumentNullException>(() => Observable.SequenceEqual(default, new[] { 42 }));
+            ReactiveAssert.Throws<ArgumentNullException>(() => Observable.SequenceEqual(default, [42]));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.SequenceEqual(DummyObservable<int>.Instance, default(IEnumerable<int>)));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.SequenceEqual(default, [42], EqualityComparer<int>.Default));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observable.SequenceEqual(DummyObservable<int>.Instance, default(IEnumerable<int>), EqualityComparer<int>.Default));

+ 1 - 1
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/SingleAsyncTest.cs

@@ -266,7 +266,7 @@ namespace ReactiveTests.Tests
             {
                 static void AssertException(Exception e)
                 {
-                    Assert.IsType(typeof(InvalidOperationException), e);
+                    Assert.IsType<InvalidOperationException>(e);
 
                     Assert.NotNull(e.StackTrace);
                     Assert.NotEqual("", e.StackTrace);

+ 2 - 0
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/SubscribeTest.cs

@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT License.
 // See the LICENSE file in the project root for more information. 
 
+#pragma warning disable IDE0350 // Use implicitly typed lambda - we want to be explicit here for clarity
+
 using System;
 using System.Collections.Generic;
 using System.Reactive;

File diff suppressed because it is too large
+ 360 - 360
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/ZipTest.cs


+ 9 - 9
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/QbservableTest.cs

@@ -484,7 +484,7 @@ namespace ReactiveTests.Tests
         [TestMethod]
         public void For_ArgumentNullChecks()
         {
-            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.For(null, new[] { 1 }, i => _qbMy));
+            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.For(null, [1], i => _qbMy));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.For(_qbp, default(IEnumerable<int>), i => _qbMy));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.For(_qbp, [1], default(Expression<Func<int, IObservable<int>>>)));
         }
@@ -492,7 +492,7 @@ namespace ReactiveTests.Tests
         [TestMethod]
         public void For()
         {
-            _qbp.For(new[] { 1 }, i => _qbMy);
+            _qbp.For([1], i => _qbMy);
         }
 
         [TestMethod]
@@ -1421,18 +1421,18 @@ namespace ReactiveTests.Tests
         [TestMethod]
         public void ToObservable_ArgumentNullChecks()
         {
-            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(null, new[] { 1 }));
+            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(null, [1]));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(_qbp, default(IEnumerable<int>)));
-            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(null, new[] { 1 }, Scheduler.Immediate));
+            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(null, [1], Scheduler.Immediate));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(_qbp, default(IEnumerable<int>), Scheduler.Immediate));
-            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(_qbp, new[] { 1 }, default));
+            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.ToObservable(_qbp, [1], default));
         }
 
         [TestMethod]
         public void ToObservable()
         {
-            _qbp.ToObservable(new[] { 1 });
-            _qbp.ToObservable(new[] { 1 }, Scheduler.Immediate);
+            _qbp.ToObservable([1]);
+            _qbp.ToObservable([1], Scheduler.Immediate);
         }
 
         [TestMethod]
@@ -1533,7 +1533,7 @@ namespace ReactiveTests.Tests
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.Zip(_qbNull, _qbMy, (a, b) => a + b));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.Zip(_qbMy, _qbNull, (a, b) => a + b));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.Zip(_qbMy, _qbMy, default(Expression<Func<int, int, int>>)));
-            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.Zip(_qbNull, new[] { 1 }, (a, b) => a + b));
+            ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.Zip(_qbNull, [1], (a, b) => a + b));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.Zip(_qbMy, default(IEnumerable<int>), (a, b) => a + b));
             ReactiveAssert.Throws<ArgumentNullException>(() => Qbservable.Zip(_qbMy, [1], default(Expression<Func<int, int, int>>)));
         }
@@ -1542,7 +1542,7 @@ namespace ReactiveTests.Tests
         public void Zip()
         {
             _qbMy.Zip(_qbMy, (a, b) => a + b);
-            _qbMy.Zip(new[] { 1 }, (a, b) => a + b);
+            _qbMy.Zip([1], (a, b) => a + b);
         }
 
         [TestMethod]

+ 2 - 0
Rx.NET/Source/tests/Tests.System.Reactive/Tests/ObserverTest.cs

@@ -95,6 +95,7 @@ namespace ReactiveTests.Tests
         [TestMethod]
         public void Create_ArgumentChecking()
         {
+#pragma warning disable IDE0350 // Use implicitly typed lambda - we want to be explicit here for clarity
             ReactiveAssert.Throws<ArgumentNullException>(() => Observer.Create<int>(default));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observer.Create<int>(default, () => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observer.Create<int>(_ => { }, default(Action)));
@@ -103,6 +104,7 @@ namespace ReactiveTests.Tests
             ReactiveAssert.Throws<ArgumentNullException>(() => Observer.Create<int>(default, (Exception _) => { }, () => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observer.Create<int>(_ => { }, default, () => { }));
             ReactiveAssert.Throws<ArgumentNullException>(() => Observer.Create<int>(_ => { }, (Exception _) => { }, default));
+#pragma warning restore IDE0350
         }
 
         [TestMethod]

+ 15 - 20
azure-pipelines.rx.yml

@@ -31,28 +31,24 @@ stages:
       BuildConfiguration: Release
       BuildPlatform: Any CPU
       DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+      # TODO: work out why the build doesn't set TargetPlatformSdkPath correctly on the build agent
+      TargetPlatformSdkPath: 'C:\Program Files (x86)\Windows Kits\10\'
 
     steps:
     - task: UseDotNet@2
-      displayName: Use .NET 8.0.x SDK
+      displayName: Use .NET 9.0.x SDK
       inputs:
-        version: 8.0.x
+        version: 9.0.x
         performMultiLevelLookup: true
+        includePreviewVersions: true
 
-    # We need .NET 7.0 and 6.0 to be able to run all tests.
-    # For .NET 7.0, the runtime package is sufficient because we don't need to build anything.
-    # That doesn't work for 6.0, because we need the desktop framework, and the only way to
-    # get that into a build agent seems to be to install the SDK.
+    # We need .NET 8.0 to be able to run all tests.
+    # We can't just use the runtime package because we need the desktop framework,
+    # and the only way to get that into a build agent seems to be to install the SDK.
     - task: UseDotNet@2
-      displayName: Use .NET 7.0 runtime
+      displayName: Use .NET 8.0 SDK
       inputs:
-        version: '7.0.x'
-        packageType: runtime
-
-    - task: UseDotNet@2
-      displayName: Use .NET 6.0 SDK
-      inputs:
-        version: '6.0.x'
+        version: '8.0.x'
 
     - task: DotNetCoreCLI@2
       inputs:
@@ -64,13 +60,12 @@ stages:
     - script: nbgv cloud -a -p Rx.NET/Source
       displayName: Set Version
 
-    - task: MSBuild@1
+    - task: DotNetCoreCLI@2
       displayName: Build System.Reactive.sln
       inputs:
-        solution: Rx.NET/Source/System.Reactive.sln
-        msbuildArguments: /restore /t:build /p:CreatePackage=true /p:NoPackageAnalysis=true /p:PackageOutputPath=$(Build.ArtifactStagingDirectory)\artifacts
-        configuration: $(BuildConfiguration)
-        maximumCpuCount: false
+        command: build
+        projects: Rx.NET/Source/System.Reactive.sln
+        arguments: -c $(BuildConfiguration) -p:CreatePackage=true -p:NoPackageAnalysis=true -p:PackageOutputPath=$(Build.ArtifactStagingDirectory)\artifacts
 
     - task: NuGetCommand@2
       displayName: Pack compatibility package
@@ -89,7 +84,7 @@ stages:
       inputs:
         command: test
         projects: Rx.NET/Source/tests/Tests.System.Reactive/*.csproj
-        arguments: -c $(BuildConfiguration) --filter "TestCategory!=SkipCI" --settings Rx.NET/Source/CodeCoverage.runsettings --collect:"XPlat Code Coverage" -- RunConfiguration.DisableAppDomain=true
+        arguments: -c $(BuildConfiguration) --no-build --filter "TestCategory!=SkipCI" --settings Rx.NET/Source/CodeCoverage.runsettings --collect:"XPlat Code Coverage" -- RunConfiguration.DisableAppDomain=true
       displayName: Run Unit Tests
 
     - task: DotNetCoreCLI@2

Some files were not shown because too many files changed in this diff