对Reverse扩展方法的突然修改导致构建错误

后端开发 2026-07-10

下面是我拥有的代码(已简化到最小程度)

using System;
using System.Linq;

namespace Test;

public class TestSomething 
{
   public byte[] DoAThing(byte[] input)
   {
      return input.Reverse().ToArray();
   }
}

实际的代码会对它做一些有用的处理,这里只是为了展示问题。

上个月一切都正常。今天在我们的git服务器上触发了一个自动化代码扫描,针对那个 Reverse 调用出现编译错误,因为显然 Reverse 现在是void?

错误CS0023: 运算符 '.' 不能应用于类型为 'void'

请注意,代码没有改变。由于公司策略,这个分支被锁定(代码进入生产后,我们会创建一个分支并锁定它,并对已投入生产的代码进行定期扫描)。

本地构建时,它编译通过。Reverse 的签名是 System.Collections.Generic.IEnumerable<byte> System.Collections.Generic.IEnumerable<byte>.Reverse()

因此它是在 IEnumerable<byte> 上的一个扩展方法,返回一个 IEnumerable<byte>,至少在我的机器上是这样。

这个项目在.NET 4.8和 .NET 8下都可以构建,在构建服务器上,在这两种框架下得到相同的错误。

我确实发现如果在顶部的usings部分加入 using static System.Linq.Enumerable;,在构建服务器上就能编译通过,但那是在锁定分支的一个分支上。如果我能通过Pull Request解决这个问题,我当然愿意,但这不被允许。

那么问题是:在我的仓库之外可能发生了什么改变?是否存在另一个命名空间中的扩展方法可以就地执行Reverse,它以某种方式找到了它?在我的仓库之外是否有某些配置强制默认的 using 语句?

解决方案

这似乎与一个在C# 14和 .NET 10中有文档记录的破坏性变更有关,即 C# 14中的第一类Span支持 的变更。

来自 第一类Span类型:破坏性变更:在数组上调用Reverse

破坏性变更

由于任何改变现有场景转换的提案都会引入一些新的破坏性变更。下面是一些例子:

在数组上调用 Reverse

当x 是类型 T[] 的一个实例时,对 x.Reverse() 的调用以前会绑定到 IEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>),现在绑定到 void MemoryExtensions.Reverse<T>(this Span<T>)。不幸的是,这些API不兼容(后者就地执行反转并返回 void)。

.NET 10通过添加一个针对数组的重载 IEnumerable<T> Reverse<T>(this T[]), 见 https://github.com/dotnet/runtime/issues/107723

void M(int[] a) { foreach (var x in a.Reverse()) { } // fine previously, an error now (`Reverse` returns `void`) foreach (var x in Enumerable.Reverse(a)) { } // workaround }

设计会议:https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse

如果你在使用C# 14对 .NET 10框架库进行构建,缓解措施应该会生效并维持当前行为。但在你的问题中你提到你使用的是 .NET 4.8和 .NET 8。在那些早期版本中第一类span尚未实现,因此同样不应有问题,因为旧的enumerable重载应该会被绑定。

然而,如果你将C# 14语法与较早的框架版本混用,或反过来[1],缓解措施将不会生效,你可能会遇到你所看到的确切编译错误。事实上,在 评论 中你也提到:

我们有一个计划任务每月运行一次,启动一个GitHub Action来执行构建。构建的一部分会让代码通过几个漏洞扫描器(Fortify和 Sonatype)。不过它们是在初始构建之后作为步骤运行,因此不太可能引起问题。哦,另外,.NET SDK是 10.0.103,尽管所讨论的项目是.NET 8和 .NET 4.8。如果重要的话,这只是一个单元测试项目。

鉴于单元测试项目使用的是.NET 10,因此基于.NET 10的扫描工具与主项目为.NET 8的组合可能是造成编译错误的原因。

或者,正如 Ivan PetrovJon Skeet 所建议,你可能在某些的 .csproj 文件中设置了 [<LangVersion>latest</LangVersion>],例如如下所示:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <LangVersion>latest</LangVersion>  <!-- Setting this manually causes the compilation error when built using a .NET 10 tool chain -->
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>Test</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

这样做将在使用.NET 10的工具链构建时产生 error CS0023: Operator '.' cannot be applied to operand of type 'void'

无论你究竟如何将.NET 8与 C# 14混用,要解决编译错误,应该在所有项目中使用一致的.NET和 C#版本——包括主项目和单元测试项目。也就是说,要么让单元测试项目使用.NET 8,要么把所有项目都切换到.NET 10。完成后,务必不要将早期的.NET版本与“未来”的C#版本混用。

如果你在某些 csproj 文件中设置了 <LangVersion>latest</LangVersion>,应将其移除,微软的文档明确警告不要使用此设置,因为它可能导致机器特定的构建失败:

警告

不要将 LangVersion 元素设置为 latestlatest 设置意味着已安装的编译器使用其最新版本。latest 的值可能会因机器而异,从而使构建不稳定。并且,它会启用在当前SDK中可能不包含的运行时或库功能所需的语言特性。

(事实上,他们的警告似乎恰好与您看到的问题吻合。)


[1] C# 14与 .NET 8的组合明确为 不受支持的组合:

C# 14仅在.NET 10及更新版本上受支持。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章