一个看似无关的 NuGet 包,能让你的 Windows 应用部署体积凭空多出90 MB。这不是夸张,而是 Rx.NET 用户在自包含部署和 Native AOT 场景下真实踩过的坑。Rx.NET 7.0 刚刚发布,核心改动只有一项:把 Windows UI 相关功能从主包里拆出去。

问题出在哪

打开网易新闻 查看精彩图片

Reactive Extensions for .NET 提供基于 IObservable 的编程模型,以及组合异步和事件驱动数据流的一系列操作符。2023 年 1 月英国公司 endjin 接手维护后,几个版本的重心一直是现代化改造和清理技术债务,而不是继续堆操作符。7.0 延续了这个思路。

麻烦集中在同时使用自包含部署或 Native AOT,以及 Windows 特定 TFM 的应用上,比如 net8.0-windows10.0.19041。只要引用了 System.Reactive,即使应用完全没碰 WPF 或 Windows Forms,完整的 WPF 和 Windows Forms 框架也可能被打进最终产物。

三档体积账

endjin 给出了具体数字:不启用裁剪时,额外增加约90 MB;启用裁剪后仍增加约47 MB;使用 Native AOT 构建,额外体积约为11 MB。对于追求精简分发的桌面应用来说,这笔账相当刺眼。

7.0 的解法是把 UI 相关功能拆成四个独立 NuGet 包:System.Reactive.Windows.Forms、System.Reactive.Wpf、System.Reactive.WindowsRuntime 和 System.Reactive.Uwp。升级后,确实用到这些集成能力的项目需要显式添加对应包;没用到的话,源代码不用改,受影响的自包含部署可以直接瘦身。

兼容性怎么保

维护团队尽量保证了拆包后的二进制兼容性。旧版 UI API 仍然保留在 NuGet 包提供的运行时程序集里,只是从引用程序集里移除了。这意味着用 Rx.NET 6.1 编译好的组件理论上还能继续运行;重新编译的应用则无法在没添加对应包的情况下,不知不觉依赖 Windows UI 功能。

System.Reactive 7.0 还加入了一个 Analyzer,可以检测受影响的源代码,并提示需要添加哪个包。这个设计让升级路径清晰了不少:老二进制能跑,新编译会收到明确提醒。

破坏性变更与特殊情况

7.0 也带来了一些破坏性变更。它不再支持 .NET 6 和 .NET 7,但继续支持 .NET 8、.NET 9、.NET 10、.NET Framework 4.7.2、.NET Standard 2.0 和 UWP。另外,OfType 的可空性标注得到修正,运行时行为没有变化,但方法签名变了,按语义化版本规则,这项改动在技术上也需要提升主版本号。

对于仍在使用旧式 packages.config 机制的项目,还有一个特殊情况:这种格式无法区分 NuGet 的 ref 和 lib 资产,所以即使没有显式引用新的 UI 包,这类项目仍可能看到这些 UI API。Rx.NET 维护团队并不支持这种配置,并表示未来可能会移除为兼容这一机制而保留的代码。

社区反馈与后续路线

此前 Rx.NET 用户已经在讨论 Windows 特定的打包问题,以及项目能否跟上现代 .NET 部署模型。有开发者反映,在某些目标框架组合下,他们不得不自行绕过缺失的 Windows Scheduler API;也有人希望 Rx.NET 进一步改善裁剪、AOT 支持以及包文档。目前 7.0 发布后的公开讨论还比较有限,判断社区整体反响为时过早。

endjin 表示,接手维护工作时制定的路线图主要目标已经完成。后续工作可能转向进一步减少内存分配、引入代码生成、探索对类似 ref 元素的支持,以及增加更多操作符。System.Reactive 7.0 已经可以通过 NuGet 获取,拆包设计和后续开发计划的细节可以在 Rx.NET GitHub 仓库中查看。