.NET Core 1.0-3.0 中的核心 .NET 库中断性变更

核心 .NET 库提供了 .NET Core 使用的基元和其他常规类型。

本页记录了以下中断性变更:

重大更改 引入的版本
将 GroupCollection 传递到采用 IEnumerable<T> 的扩展方法需要消除歧义 .NET Core 3.0
报告版本的 API 现在报告产品版本而不是文件版本 .NET Core 3.0
自定义 EncoderFallbackBuffer 实例无法递归回退 .NET Core 3.0
浮点格式设置和分析行为变更 .NET Core 3.0
浮点分析操作不再失败或引发 OverflowException .NET Core 3.0
InvalidAsynchronousStateException 已移到另一个程序集 .NET Core 3.0
替换格式错误的 UTF-8 字节序列将遵循 Unicode 准则 .NET Core 3.0
TypeDescriptionProviderAttribute 已移到另一个程序集 .NET Core 3.0
ZipArchiveEntry 不再处理条目大小不一致的存档 .NET Core 3.0
FieldInfo.SetValue 将对静态、仅初始化字段引发异常 .NET Core 3.0
路径 API 对无效字符不引发异常 .NET Core 2.1
添加到内置结构类型的私有字段 .NET Core 2.1
UseShellExecute 默认值更改 .NET Core 2.1
macOS 上的 OpenSSL 版本 .NET Core 2.1
FileSystemInfo.Attributes 引发的 UnauthorizedAccessException .NET Core 1.0
不支持处理损坏的进程状态异常 .NET Core 1.0
UriBuilder 属性不再预置前导字符 .NET Core 1.0
Process.StartInfo 对未启动的进程引发 InvalidOperationException .NET Core 1.0

.NET Core 3.0

将 GroupCollection 传递到采用 IEnumerable<T> 的扩展方法需要消除歧义

调用在 GroupCollection 上采用 IEnumerable<T> 的扩展方法时,必须使用强制转换来消除该类型的歧义。

更改说明

从 .NET Core 3.0 开始,System.Text.RegularExpressions.GroupCollection 除了实现 IEnumerable<Group> 等类型之外,还会实现 IEnumerable<KeyValuePair<String,Group>>。 调用采用 IEnumerable<T> 的扩展方法时,这会导致歧义。 如果在 GroupCollection 实例上调用此类扩展方法,例如 Enumerable.Count,你将看到以下编译器错误:

CS1061:“GroupCollection”未包含“Count”的定义,并且找不到可接受第一个“GroupCollection”类型参数的可访问扩展方法“Count”(是否缺少 using 指令或程序集引用?)

在早期版本的 .NET 中,没有歧义,也没有编译器错误。

引入的版本

3.0

更改原因

这是意外的中断性变更。 由于此更改已经有一段时间了,所以我们不打算将其还原。 此外,此类更改本身就会造成中断。

对于 GroupCollection 实例,使用强制转换对接受 IEnumerable<T> 的扩展方法的调用消除歧义。

// Without a cast - causes CS1061.
match.Groups.Count(_ => true)

// With a disambiguating cast.
((IEnumerable<Group>)m.Groups).Count(_ => true);

类别

Core .NET 库

受影响的 API

任何接受 IEnumerable<T> 的扩展方法都会受到影响。 例如:


报告版本的 API 现报告的是产品版本而不是文件版本

返回 .NET Core 中的版本的很多 API 现在返回的是产品版本,而不是文件版本。

更改描述

在 .NET Core 2.2 及更低版本中,Environment.VersionRuntimeInformation.FrameworkDescription 等方法以及 .NET Core 程序集的“文件属性”对话框反映的都是文件版本。 自 .NET Core 3.0 起,它们反映的是产品版本。

下图展示了由“Windows 资源管理器”文件属性对话框显示的 .NET Core 2.2(左侧)与 .NET Core 3.0(右侧)的 System.Runtime.dll 程序集版本信息区别 。

Difference in product version information

引入的版本

3.0

无。 此更改会使版本检测变得直观而非退化。

类别

Core .NET 库

受影响的 API


自定义 EncoderFallbackBuffer 实例无法递归回退

自定义 EncoderFallbackBuffer 实例无法以递归方式回退。 EncoderFallbackBuffer.GetNextChar() 的实现必须生成一个可转换为目标编码的字符序列。 否则会发生异常。

更改描述

在字符到字节的转码操作期间,运行时将检测格式不正确或不可转换的 UTF-16 序列,并将这些字符提供给 EncoderFallbackBuffer.Fallback 方法。 Fallback 方法确定应将哪些字符替换为原始不可转换数据,并通过在循环中调用 EncoderFallbackBuffer.GetNextChar 来释放这些字符。

然后,运行时尝试将这些替换字符转码为目标编码。 如果此操作成功,则运行时继续从原始输入字符串中的中断位置进行转码。

之前,EncoderFallbackBuffer.GetNextChar() 的自定义实现可以返回无法转换为目标编码的字符序列。 如果替换字符无法转码为目标编码,则运行时将使用替换字符再调用一次 EncoderFallbackBuffer.Fallback 方法,并要求 EncoderFallbackBuffer.GetNextChar() 方法返回新的替换序列。 此过程将一直继续,直到运行时最终看到格式正确的、可转换的替换,或直到达到最大递归计数。

从 .NET Core 3.0 开始,EncoderFallbackBuffer.GetNextChar() 的自定义实现必须返回可转换为目标编码的字符序列。 如果替换字符无法转码为目标编码,则引发 ArgumentException。 运行时将不再对 EncoderFallbackBuffer 实例进行递归调用。

仅当满足以下所有三个条件时,此行为才适用:

  • 运行时检测格式不正确的 UTF-16 序列或无法转换为目标编码的 UTF-16 序列。
  • 已指定自定义 EncoderFallback
  • 自定义 EncoderFallback 尝试替换新的格式不正确的或无法转换的 UTF-16 序列。

引入的版本

3.0

大多数开发人员都不需要执行任何操作。

如果应用程序使用自定义 EncoderFallbackEncoderFallbackBuffer 类,请确保 EncoderFallbackBuffer.Fallback 的实现使用格式正确的 UTF-16 数据(该数据在运行时第一次调用 Fallback 方法时可直接转换为目标编码)填充回退缓冲区。

类别

Core .NET 库

受影响的 API


浮点格式设置和分析行为已更改

浮点分析和格式设置行为(由 DoubleSingle 类型实现)现符合 IEEE 标准。 这可确保 .NET 中浮点类型的行为与符合 IEEE 标准的语言的行为一致。 例如,double.Parse("SomeLiteral") 应始终与 C# 为 double x = SomeLiteral 生成的结果匹配。

更改描述

在 .NET Core 2.2 及更低版本中,通过 Double.ToStringSingle.ToString 进行格式设置的行为,以及使用 Double.ParseDouble.TryParseSingle.ParseSingle.TryParse 进行分析的行为不符合 IEEE 标准。 因此,没法保证值在没有任何受支持的标准或自定义格式字符串的情况下双向传输。 对于某些输入,尝试分析已设置格式的值可能会失败;而对于其他输入,分析后的值与原始值不相等。

自 .NET Core 3.0 起,浮点分析和格式设置操作均符合 IEEE 754 标准。

下表显示了两个代码片段,以及 .NET Core 2.2 和 .NET Core 3.1 之间的输出更改情况。

代码片段 .NET Core 2.2 上的输出 .NET Core 3.1 上的输出
Console.WriteLine((-0.0).ToString()); 0 -0
var value = -3.123456789123456789;
Console.WriteLine(value == double.Parse(value.ToString()));
False True

有关浮点改进的详细信息,请参阅 Floating-point parsing and formatting improvements in .NET Core 3.0(.NET Core 3.0 中浮点分析和格式设置改进)博客文章。

引入的版本

3.0

.NET Core 3.0 中浮点分析和格式设置改进博客文章的“对现有代码的潜在影响”部分建议,如果要维护以前的行为,可以对代码进行一些更改。

  • 对于格式设置中的一些差异,可以通过指定不同的格式字符串获得与以前行为等效的行为。
  • 对于分析方面的差异,没有回退到以前行为的机制。

类别

Core .NET 库

受影响的 API


浮点分析操作不再失败或引发 OverflowException

浮点分析方法在分析数值超出 SingleDouble 浮点类型范围的字符串时,不再引发 OverflowException 或返回 false

更改描述

在 .NET Core2.2 和早期版本中,Double.ParseSingle.Parse 方法对超出其各自类型范围的值会引发 OverflowException。 对于超出范围的数值的字符串表示形式,Double.TryParseSingle.TryParse 方法返回 false

从 .NET Core 3.0 开始,分析超出范围的数值字符串时,Double.ParseDouble.TryParseSingle.ParseSingle.TryParse 方法不再失败。 相反,Double 分析方法对于超过 Double.MaxValue 的值返回 Double.PositiveInfinity,对于小于 Double.MinValue 的值返回 Double.NegativeInfinity。 同样,Single 分析方法对于超过 Single.MaxValue 的值返回 Single.PositiveInfinity,对于小于 Single.MinValue 的值返回 Single.NegativeInfinity

此更改是为了改进 IEEE 754:2008 的符合性。

引入的版本

3.0

此更改会以两种方式之一影响你的代码:

  • 你的代码依赖于 OverflowException 在发生溢出时执行的处理程序。 在这种情况下,应删除 catch 语句,并在 If 语句中放置必要的代码,以测试 Double.IsInfinitySingle.IsInfinity 是否为 true

  • 代码假设浮点值不是 Infinity。 在这种情况下,应添加必要的代码来检查 PositiveInfinityNegativeInfinity 的浮点值。

类别

Core .NET 库

受影响的 API


InvalidAsynchronousStateException 已移到另一程序集

InvalidAsynchronousStateException 类已移动。

更改描述

在 .NET Core 2.2 及更低版本中,InvalidAsynchronousStateException 位于 System.ComponentModel.TypeConverter 程序集中 。

而自 .NET Core 3.0 起,它位于 System.ComponentModel.Primitives 程序集中 。

引入的版本

3.0

此更改仅影响这样的应用程序,它们通过调用 Assembly.GetType 等方法或调用假设类型位于特定程序集中的 Activator.CreateInstance 的重载,使用反射过程来加载 InvalidAsynchronousStateException。 如果是这种情况,可以更新在方法调用中引用的程序集,以反映出类型的新程序集位置。

类别

Core .NET 库

受影响的 API

无。


替换格式错误的 UTF-8 字节序列将遵循 Unicode 准则

UTF8Encoding 类在字节到字符转码操作期间遇到格式错误的 UTF-8 字节序列时,它将在输出字符串中用“�”(U+FFFD 替换字符)替换该序列。 .NET Core 3.0 与以前版本的 .NET Core 和 .NET Framework 的不同之处在于,在转码操作期间按照 Unicode 最佳做法执行此替换。

这是在整个 .NET 中改进 UTF-8 处理的较大工作量(包括通过新的 System.Text.Unicode.Utf8System.Text.Rune 类型)的一部分。 为 UTF8Encoding 类型提供了改进的错误处理机制,以便生成与新引入的类型一致的输出。

更改描述

从 .NET Core 3.0 开始,当将字节转码为字符时,UTF8Encoding 类会根据 Unicode 最佳做法执行字符替换。 Unicode 标准 12.0 版第 3.9 节 (PDF) 的 “U+FFFD 替换最大子部分”标题中描述了使用的替换机制。

仅当 输入字节序列包含格式错误的 UTF-8 数据时,此行为才适用。 此外,如果已通过 throwOnInvalidBytes: true 构造了 UTF8Encoding 实例,则 UTF8Encoding 实例将继续对无效输入引发,而不是执行 U+FFFD 替换。 有关 UTF8Encoding 构造函数的详细信息,请参阅 UTF8Encoding(Boolean, Boolean)

下表通过无效的 3 字节输入说明了此更改的影响:

格式无效的 3 字节输入 .NET Core 3.0 之前的输出 从 .NET Core 3.0 开始的输出
[ ED A0 90 ] [ FFFD FFFD ](2 字符输出) [ FFFD FFFD FFFD ](3 字符输出)

根据以前链接的 Unicode 标准 PDF 的表 3-9 ,此 3 字符输出为首选输出。

引入的版本

3.0

开发人员一方不需要执行任何操作。

类别

Core .NET 库

受影响的 API


TypeDescriptionProviderAttribute 已移到另一程序集

TypeDescriptionProviderAttribute 类已移动。

更改描述

在 .NET Core 2.2 及更低版本中,TypeDescriptionProviderAttribute 位于 System.ComponentModel.TypeConverter 程序集中。

而自 .NET Core 3.0 起,它位于 System.ObjectModel 程序集中

引入的版本

3.0

此更改仅影响这样的应用程序,它们通过调用 Assembly.GetType 等方法或调用假设类型位于特定程序集中的 Activator.CreateInstance 的重载,使用反射过程来加载 TypeDescriptionProviderAttribute 类型。 若是如此,应更新在方法调用反射的程序集,以反射出类型的新程序集位置。

类别

Windows 窗体

受影响的 API

无。


ZipArchiveEntry 不再处理具有不一致条目大小的存档

Zip 存档在中央目录和本地标头中列出压缩的大小和未压缩的大小。 条目数据本身还指示其大小。 在 .NET Core 2.2 及更早版本中,永远不会对这些值进行一致性检查。 从 .NET Core 3.0 开始,对它们进行一致性检查。

更改描述

在 .NET Core 2.2 及更早版本中,即使本地标头与 zip 文件的中央标头不一致,ZipArchiveEntry.Open() 也会成功。 即使数据长度超出了中央目录/本地标头中列出的未压缩文件大小,数据也会一直进行解压缩,直到达到压缩流的末尾。

从 .NET Core 3.0 开始,ZipArchiveEntry.Open() 方法会检查本地标头和中央标头是否在条目的压缩大小和未压缩大小方面保持一致。 如果不是这样,则该方法在存档的本地标头和/或数据描述符列表大小与 zip 文件的中央目录不一致时会引发 InvalidDataException。 当读取条目时,解压缩的数据将被截断为标头中列出的未压缩文件大小。

进行此更改是为了确保 ZipArchiveEntry 正确表示其数据的大小并且只读取该数据量。

引入的版本

3.0

对出现这些问题的所有 zip 存档重新打包。

类别

Core .NET 库

受影响的 API


FieldInfo.SetValue 将对静态、仅初始化字段引发异常

从 .NET Core 3.0 开始,当你尝试通过调用 System.Reflection.FieldInfo.SetValue 在静态 InitOnly 字段上设置值时,将引发异常。

更改描述

在 .NET Framework 和 3.0 之前的 .NET Core 版本中,你可以通过调用 System.Reflection.FieldInfo.SetValue 来设置初始化后为常量的静态字段的值(C# 中的 readonly)。 但是,以这种方式设置此类字段导致了不可预测的行为,具体取决于目标框架和优化设置。

在 .NET Core 3.0 及更高版本中,当对静态 InitOnly 字段调用 SetValue 时,将引发 System.FieldAccessException 异常。

提示

InitOnly 字段是只能在声明它时或位于包含类的构造函数中时设置的字段。 换句话说,它在初始化后为常量。

引入的版本

3.0

初始化静态构造函数中的静态 InitOnly 字段。 这同时适用于动态和非动态类型。

或者,可以从字段中删除 FieldAttributes.InitOnly 属性,然后调用 FieldInfo.SetValue

类别

Core .NET 库

受影响的 API


.NET Core 2.1

路径 API 对无效字符不引发异常

如果找到无效字符,涉及文件路径的 API 将不再验证路径字符或引发 ArgumentException

更改说明

在 .NET Framework 和 .NET Core 1.0-2.0 中,如果路径参数包含无效路径字符,受影响的 API 部分中列出的方法便会引发 ArgumentException。 从 .NET Core 2.1 开始,如果找到无效字符,这些方法就不再检查无效路径字符或引发异常。

更改原因

主动验证路径字符会对某些跨平台场景形成阻碍。 引入此更改是为了使 .NET 不尝试复制或预测操作系统 API 调用的结果。 有关详细信息,请参阅博客文章 .Net Core 2.1 中的 System.IO 速览

引入的版本

.NET Core 2.1

如果代码依赖这些 API 来检查无效字符,则可以添加对 Path.GetInvalidPathChars 的调用。

受影响的 API

另请参阅


添加到内置结构类型的私有字段

私有字段已添加到引用程序集中的特定结构类型。 因此,在 C# 中,必须始终使用 new 运算符默认文本来实例化结构类型。

更改描述

在 .NET Core 2.0 和早期版本中,某些提供的结构类型(例如 ConsoleKeyInfo)可以在不使用 new 运算符或默认文本的情况下在 C# 中实例化。 这是因为 C# 编译器使用的引用程序集不包含结构的私有字段。 从 .NET Core 2.1 开始,.NET 结构类型的所有私有字段都将添加到引用程序集。

例如,下面的 C# 代码在 .NET Core 2.0 中编译,但不在 .Net core 2.1 中编译:

ConsoleKeyInfo key;    // Struct type

if (key.ToString() == "y")
{
    Console.WriteLine("Yes!");
}

在 .NET Core 2.1 中,之前的代码会导致以下编译器错误:CS0165 - 使用了未赋值的局部变量“key”

引入的版本

2.1

使用 new 运算符或默认文本实例化结构类型。

例如:

ConsoleKeyInfo key = new ConsoleKeyInfo();    // Struct type.

if (key.ToString() == "y")
    Console.WriteLine("Yes!");
ConsoleKeyInfo key = default;    // Struct type.

if (key.ToString() == "y")
    Console.WriteLine("Yes!");

类别

Core .NET 库

受影响的 API


UseShellExecute 默认值更改

ProcessStartInfo.UseShellExecute 在 .NET Core 上的默认值为 false。 在 .NET Framework 上,其默认值为 true

更改描述

可以通过 Process.Start 直接启动应用程序,例如,使用 Process.Start("mspaint.exe") 代码启动画图。 如果 ProcessStartInfo.UseShellExecute 设置为 true,它还允许间接启动关联的应用程序。 在 .NET Framework 上,ProcessStartInfo.UseShellExecute 的默认值 true,这意味着,如果已将 .txt 文件与该编辑器相关联,则 Process.Start("mytextfile.txt") 会启动记事本。 若要防止在 .NET Framework 上间接启动应用,必须将 ProcessStartInfo.UseShellExecute 显式设置为 false。 在 .NET Core 中,ProcessStartInfo.UseShellExecute 的默认值为 false。 这意味着,默认情况下,在调用 Process.Start 时不会启动关联的应用程序。

只有当 ProcessStartInfo.UseShellExecutetrue 时,System.Diagnostics.ProcessStartInfo 上的以下属性才起作用:

出于性能方面的考虑,.NET Core 中引入了此更改。 通常情况下,Process.Start 用于直接启动应用程序。 直接启动应用并不需要使用 Windows shell,也不会产生关联的性能成本。 为了更快地使此情况默认化,.NET Core 将 ProcessStartInfo.UseShellExecute 的默认值更改为 false。 如果需要,可以选择慢速路径。

引入的版本

2.1

注意

在早期版本的 .NET Core 中,没有为 Windows 实现 UseShellExecute

如果应用依赖于旧行为,请调用 Process.Start(ProcessStartInfo),并将 UseShellExecute 设置为 ProcessStartInfo 对象上的 true

类别

Core .NET 库

受影响的 API


macOS 上的 OpenSSL 版本

对于 AesCcmAesGcmDSAOpenSslECDiffieHellmanOpenSslECDsaOpenSslRSAOpenSslSafeEvpPKeyHandle 类型,macOS 上的 .NET Core 3.0 及更高版本的运行时现在首选 OpenSSL 1.1.x 版而非 OpenSSL 1.0.x 版。

.NET Core 2.1 运行时现在支持 OpenSSL 1.1.x 版本,但仍首选 OpenSSL 1.0.x 版。

更改描述

以前,.NET Core 运行时在 macOS 上使用 OpenSSL 1.0.x 版处理与 OpenSSL 交互的类型。 最新的 OpenSSL 1.0.x 版 OpenSSL 1.0.2 现已不受支持。 若要在支持的 OpenSSL 版本上保留使用 OpenSSL 的类型,.NET Core 3.0 及更高版本的运行时现需在 macOS 上使用较新版本的 OpenSSL。

通过此更改,macOS 上的 .NET Core 运行时的行为如下所示:

  • .NET Core 3.0 及更高版本运行时使用 OpenSSL 1.1.x(如果可用),并且仅在没有 1.1.x 版本可用的情况下才回退到 OpenSSL 1.0.x。

    对于将 OpenSSL 互操作类型与自定义 P/Invoke 一起使用的调用方,请按照 SafeEvpPKeyHandle.OpenSslVersion 注释中的指南进行操作。 如果不检查 OpenSslVersion 值,你的应用可能会出现故障。

  • .NET Core 2.1 运行时使用 OpenSSL 1.0.x(如果可用),并且仅在没有 1.0.x 版本可用的情况下才回退到 OpenSSL 1.1.x。

    2\.1 运行时首选早期版本的 OpenSSL,因为 .NET Core 2.1 中不存在 SafeEvpPKeyHandle.OpenSslVersion 属性,因此无法在运行时可靠地确定 OpenSSL 版本。

引入的版本

  • .NET Core 2.1.16
  • .NET Core 3.0.3
  • .NET Core 3.1.2

类别

Core .NET 库

受影响的 API


.NET Core 1.0

FileSystemInfo.Attributes 引发的 UnauthorizedAccessException

在 .NET Core 中,当调用方尝试设置文件属性值但没有写权限时,将引发 UnauthorizedAccessException

更改描述

在 .NET Framework 中,当调用方尝试在 FileSystemInfo.Attributes 中设置文件属性值但没有写权限时,将引发 ArgumentException。 在 .NET Core 中,将改为引发 UnauthorizedAccessException。 (在 .NET Core 中,如果调用方尝试设置无效的文件属性,则仍会引发 ArgumentException。)

引入的版本

1.0

根据需要修改任何 catch 语句不是捕获 ArgumentException,而是捕获或者除它之外还捕获 UnauthorizedAccessException

类别

Core .NET 库

受影响的 API


不支持处理损坏状态异常

不支持在 .NET Core 中处理损坏进程状态异常。

更改描述

以前,损坏进程状态异常可以由托管代码异常处理程序进行捕获和处理,例如在 C# 中使用 try-catch 语句。

从 .NET Core 1.0 开始,损坏进程状态异常无法由托管代码进行处理。 公共语言运行时不会将损坏进程状态异常传递给托管代码。

引入的版本

1.0

通过解决导致这些异常的情况来避免需要处理损坏进程状态异常。 如果绝对有必要处理损坏进程状态异常,请在 C 或 C++ 代码中编写异常处理程序。

类别

Core .NET 库

受影响的 API


UriBuilder 属性不再预置前导字符

如果已存在一个字符,则 UriBuilder.Fragment 不再预置前导 # 字符,且 UriBuilder.Query 不再预置前导 ? 字符。

更改描述

在 .NET Framework 中,UriBuilder.FragmentUriBuilder.Query 属性始终将 #? 字符分别预置到所存储的值。 如果字符串已经包含其中一个前导字符,则此行为可能会导致存储值中包含多个 #? 字符。 例如,UriBuilder.Fragment 的值可能会变为 ##main

从 .NET Core 1.0 开始,如果在字符串的开头已经存在一个字符,则这些属性将不再将 #? 字符预置到存储值之前。

引入的版本

1.0

设置属性值时,不再需要显式删除这些前导字符。 这在追加值时特别有用,因为不再需要在每次追加时删除前导 #?

例如,下面的代码片段显示 .NET Framework 和 .NET Core 之间的行为差异。

var builder = new UriBuilder();
builder.Query = "one=1";
builder.Query += "&two=2";
builder.Query += "&three=3";
builder.Query += "&four=4";

Console.WriteLine(builder.Query);
  • 在 .NET Framework 中,输出为 ????one=1&two=2&three=3&four=4
  • 在 .NET Core 中,输出为 ?one=1&two=2&three=3&four=4

类别

Core .NET 库

受影响的 API


Process.StartInfo 对未启动的进程引发 InvalidOperationException

对于你的代码未启动的进程,读取其 Process.StartInfo 属性会引发 InvalidOperationException

更改描述

在 .NET Framework 中,访问你的代码未启动的进程的 Process.StartInfo 属性将返回虚拟 ProcessStartInfo 对象。 虚拟对象包含其所有属性(EnvironmentVariables 除外)的默认值。

从 .NET Core 1.0 开始,如果读取你未启动的进程的 Process.StartInfo 属性(即通过调用 Process.Start),则会引发 InvalidOperationException

引入的版本

1.0

不要访问你的代码未启动的进程的 Process.StartInfo 属性。 例如,不要读取 Process.GetProcesses 返回的进程的此属性。

类别

Core .NET 库

受影响的 API