.NET 10 更改 BufferedStream.WriteByte:不再隐式触发 Flush
在 .NET 9 及更早版本中,BufferedStream.WriteByte 在写入单字节恰好触及内部缓冲区容量边界时,会有一个容易被忽视的副作用:它会顺带调用被包装流的 Flush() 方法。.NET 10 移除了这一隐式刷新行为。字节在需要腾出缓冲区空间时仍然会被写入底层流,但达到缓冲区边界不再自动成为一次 Flush 的触发点。对于将 Flush() 赋予可观测含义的自定义流、协议适配器、压缩器或测试替身来说,这一差别尤其重要。
微软已将这一行为变化列入 .NET 10 的正式变更清单。过去 WriteByte 与其他 BufferedStream.Write 方法存在不一致:当写入触及容量边界时,WriteByte 会额外调用底层流的 Flush();.NET 10 统一了这一行为,不再有该额外调用。应用最终发送的字节序列可能与以前一致,但不再会在某个特定时间点产生此前那个隐含的副作用。这个新的行为在 .NET 10 LTS 中被视为稳定行为,而非预览特性。
为验证差异,作者在 SDK 10.0.303 和运行时 10.0.11 上测试,并用 9.0.18 运行时作为对照。需要澄清“flush”一词的含义:BufferedStream 可以在不调用目标流的 Flush() 方法的情况下,将缓冲字节写入目标以腾出空间。这次破坏性变更针对的是对目标 Flush() 的调用,而不是承诺缓冲区内的字节会一直停留直到显式释放。作者建议用可执行契约来验证行为,而不是仅依赖 MemoryStream.Length 等猜测式检查。 龙8头号玩家国际
作者构造了一个多目标验证器:用一个包装的 MemoryStream 分别统计写入次数和 Flush 调用次数。测试设定为四字节缓冲区并写入恰好四个字节。未显式调用 buffered.Flush() 前的结果如下: - 在 net9.0 运行时:flushes=1,writes=1,bytes=010203 - 在 net10.0 运行时:flushes=0,writes=1,bytes=010203
两个运行时都会先将前三个字节写入目标以腾出空间,并把第四个字节留在缓冲区中;但只有 .NET 9 会在目标上调用 Flush()。在随后显式调用 buffered.Flush() 之后,两个目标流都会按顺序包含 01020304。