C# 开发人员的版本和更新注意事项

兼容性是一个重要目标,因为向 C# 语言添加了新功能。 几乎所有情况下,都可以使用新的编译器版本重新编译现有代码,且没有任何问题。 .NET 运行时团队还的目标是确保更新库的兼容性。 几乎所有情况下,当你的应用从更新的运行时启动并更新的库时,行为与以前的版本完全相同。

用于编译应用的语言版本通常与项目中引用的运行时目标框架名字对象(TFM)匹配。 有关更改默认语言版本的详细信息,请参阅标题为 配置语言版本的文章。 此默认行为可确保最大兼容性。

引入 中断性变更 时,它们被归类为:

  • 二进制中断性变更:使用新运行时启动时,二进制中断性变更会导致应用程序或库中出现不同的行为,包括可能崩溃。 必须重新编译应用才能合并这些更改。 现有二进制文件无法正常工作。
  • 源中断性变更:源中断性变更更改源代码的含义。 在使用最新版本编译应用程序之前,需要进行源代码编辑。 现有二进制文件将使用较新的主机和运行时正确运行。 请注意,对于语言语法, 源中断性变更 也是 行为更改,如 运行时中断性变更中定义。

当二进制中断性变更影响应用时,必须重新编译应用,但无需编辑任何源代码。 当源中断性变更影响应用时,现有二进制文件仍可在具有更新的运行时和库的环境中正确运行。 但是,必须进行源更改才能使用新的语言版本和运行时重新编译。 如果更改同时是源中断和二进制中断,则必须使用最新版本重新编译应用程序并进行源更新。

由于避免 C# 语言团队和运行时团队的重大更改的目标,更新应用程序通常是更新 TFM 和重新生成应用的问题。 但是,对于公开分发的库,应仔细评估受支持 TPM 和受支持的语言版本的策略。 你可能会使用最新版本中找到的功能创建新库,并且需要确保使用早期版本的编译器生成的应用可以使用它。 或者,你可能正在升级现有库,并且许多用户可能尚未升级版本。

介绍库中的重大更改

在库的公共 API 中采用新的语言功能时,应评估是否为库的用户引入二进制或源中断性变更。 在内部实现中 public 未显示的任何更改或 protected 接口都兼容。

注释

如果使用 System.Runtime.CompilerServices.InternalsVisibleToAttribute 启用类型来查看内部成员,则内部成员可以引入重大更改。

二进制中断性变更要求用户重新编译其代码才能使用新版本。 例如,请考虑以下公共方法:

public double CalculateSquare(double value) => value * value;

如果将修饰符添加到 in 方法,则这是二进制中断性变更:

public double CalculateSquare(in double value) => value * value;

用户必须重新编译使用 CalculateSquare 新库方法的任何应用程序才能正常工作。

源中断性变更要求用户在重新编译之前更改其代码。 例如,请考虑以下类型:

public class Person
{
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);

    // other details omitted
}

在较新版本中,你希望利用为 record 类型生成的合成成员。 进行以下更改:

public record class Person(string FirstName, string LastName);

以前的更改需要对派生自 Person的任何类型进行更改。 所有这些声明都必须将 record 修饰符添加到其声明中。

中断性变更的影响

向库添加 二进制中断性变更 时,强制使用库重新编译的所有项目。 但是,这些项目中没有任何源代码需要更改。 因此,对每个项目来说,中断性变更的影响相当小。

对库进行 源中断性变更 时,需要所有项目进行源更改才能使用新库。 如果需要更改,则需要新的语言功能,则强制这些项目升级到当前使用的同一语言版本和 TFM。 你还需要为用户执行更多工作,并可能强制他们升级。

所做的任何中断性变更的影响取决于依赖于库的项目数。 如果库由几个应用程序在内部使用,则可以对所有受影响的项目中的任何中断性变更做出反应。 但是,如果已公开下载库,则应评估潜在影响,并考虑替代方法:

  • 可以添加并行现有 API 的新 API。
  • 可以考虑针对不同 TTFM 的并行生成。
  • 可以考虑使用多目标。