String.Trim*(params ReadOnlySpan<char>) 重载已删除
在 .NET 生态系统中, ReadOnlySpan<char>
可以表示:
- 特定字符序列,通常作为较大 System.String 实例的切片。
- 单个字符的集合,通常作为一个
char[]
切片。
早期版本的 .NET 9 向已具有params T[]
重载的方法组添加了params ReadOnlySpan<T>
重载。 虽然此重载是某些方法组的积极添加,但对于接受和char[]
String (在同一位置)的方法组而言,这种双重性质ReadOnlySpan<char>
可能会导致混淆,并且它们受到不同的处理。 例如, public static string [String::]Split(string separator, StringSplitOptions options)
将字符序列视为一个分隔符。 例如, "[]ne]-[Tw[]".Split("]-[", StringSplitOptions.None)
拆分为 new string[] { "[]ne", "Tw[]" };
. 另一方面, public static [String::]Split(char[] separator, StringSplitOptions options)
将每个字符 separator
视为不同的分隔符,因此数组等效的拆分生成 new string[] { "", "", "ne", "", "", "Tw", "", "" }
。 因此,任何接受新 ReadOnlySpan<char>
重载,必须确定它是否与字符串类似或数组一样。 一般来说,.NET 符合类似数组的行为。
请考虑以下接受 dotnet/runtime#77873 中建议的参数的新String重载ReadOnlySpan<char>
:
public string[] Split(params ReadOnlySpan<char> separator);
public string Trim(params ReadOnlySpan<char> trimChars);
public string TrimStart(params ReadOnlySpan<char> trimChars);
public string TrimEnd(params ReadOnlySpan<char> trimChars);
此外,请考虑以下常用的扩展方法:
public static class SomeExtensions {
public static string TrimEnd(this string target, string trimString) {
if (target.EndsWith(trimString) {
return target.Substring(0, target.Length - trimString.Length);
}
return target;
}
}
对于现有的 .NET 运行时,此扩展方法从字符串末尾删除指定的序列。 但是,由于 C# 的重载解析规则, "12345!!!!".TrimEnd("!!!")
将首选新 TrimEnd
重载而不是现有扩展方法,并将结果从 "12345!"
(仅删除一组完整的三个感叹号)更改为 "12345"
(从末尾删除所有感叹号)。
若要解决此中断问题,有两个可能的路径:引入实例方法 public string TrimEnd(string trimString)
是更好的目标,或删除新方法。 第一个选项会产生额外的风险,因为它需要决定是否返回目标字符串的一个实例或所有这些实例。 毫无疑问,存在使用每个方法的现有代码的调用方。 因此,第二个选项是发布周期的此阶段最合适的选择。
例如,str.Trim(';', ',', '.')
使用params
该功能传入单个字符的调用方String.Trim不会看到中断。 代码将自动从调用 string.Trim(params char[])
切换到 string.Trim(params ReadOnlySpan<char>)
。 根据 .NET 9 的 GA 版本重新生成时,编译器将自动切换回 char[]
重载。
显式传入(ReadOnlySpan<char>
或可转换为不可转换的类型char[]
ReadOnlySpan<char>
)的调用方String.Trim必须更改其代码,才能在此更改后成功调用Trim
。
至于 String.Split,与上一样 String.Trim,此方法已具有一个 重载 ,既首选于接受单个字符串参数的扩展方法,也具有新添加 ReadOnlySpan<char>
的重载。 因此,保留了新重载 String.Split 。
注意
应重新生成针对 .NET 9 预览版 6、.NET 9 预览版 7、.NET 9 RC1 或 .NET 9 RC2 生成的任何程序集,以确保删除对已删除方法的任何调用。 否则可能会导致 MissingMethodException 运行时发生此情况。
引入的版本
.NET 9 正式版
旧行为
在 .NET 9 预览版 6、.NET 9 预览版 7、.NET 9 RC1 和 .NET 9 RC2 中编译的以下代码:
private static readonly char[] s_allowedWhitespace = { ' ', '\t', '\u00A0', '\u2000' };
// Only remove the ASCII whitespace.
str = str.Trim(s_allowedWhitespace.AsSpan(0, 2));
在 .NET 9 预览版 6 之前,将生成 "prefixinfix"
以下代码。 对于 .NET 9 预览版 6 到 .NET 9 RC2,它改为生成 "prefixin"
:
internal static string TrimEnd(this string target, string suffix)
{
if (target.EndsWith(suffix))
{
return target.Substring(0, target.Length - suffix.Length);
}
return target;
}
...
return "prefixinfixsuffix".TrimEnd("suffix");
新行为
以下显式使用数组切片的代码不再编译,因为它无法调用合适的重载:
private static readonly char[] s_allowedWhitespace = { ' ', '\t', '\u00A0', '\u2000' };
// Only remove the ASCII whitespace.
str = str.Trim(s_allowedWhitespace.AsSpan(0, 2));
具有扩展方法 string TrimEnd(this string target, this string suffix)
的代码现在的行为与 .NET 8 和早期版本中的行为相同。 也就是说,它产生 "prefixinfix"
。
中断性变更的类型
更改原因
许多项目都有扩展方法,这些方法在重新编译后体验行为更改。 这些新实例方法的负面影响被认为超过其积极利益。
建议的操作
重新编译针对 .NET 9 预览版 6、.NET 9 预览版 7、.NET 9 RC1 或 .NET 9 RC2 生成的任何项目。 如果项目编译时没有错误,则无需执行进一步的工作。 如果项目不再编译,请调整代码。 下面显示了一个可能的替换示例:
-private static ReadOnlySpan<char> s_trimChars = [ ';', ',', '.' ];
+private static readonly char[] s_trimChars = [ ';', ',', '.' ];
...
return input.Trim(s_trimChars);
受影响的 API
System.String.Trim(System.ReadOnlySpan{System.Char})
System.String.TrimEnd(System.ReadOnlySpan{System.Char})
System.String.TrimStart(System.ReadOnlySpan{System.Char})