C# 13 中的新增功能

C# 13 包括以下新增功能。 可以使用最新的 Visual Studio 2022 版本或 .NET 9 SDK 尝试这些功能:

从 Visual Studio 17.12 开始,C# 13 将 field 上下文关键字列为预览功能。

C# 13 支持在 .NET 9上。 有关详细信息,请参阅 C# 语言版本控制

可以通过 .NET 下载页下载最新 9 .NET SDK。 还可以下载 Visual Studio 2022,其中包括 .NET 9 SDK。

新功能一旦在公开预览版中发布,就会被添加到“C# 新特性”页中。 roslyn 功能状态页工作集部分跟踪即将推出的功能何时合并到主分支中。

可以在我们关于重大更改的文章中找到 C# 13 中引入的任何重大更改。

注意

我们有兴趣了解你对这些功能的反馈。 如果发现这些新功能存在问题,请在 dotnet/roslyn 存储库中创建新问题

params 集合

params 修饰符并不局限于数组类型。 现在可以将 params 用于任何已识别的集合类型,包括 System.Span<T>System.ReadOnlySpan<T>,以及那些实现 System.Collections.Generic.IEnumerable<T> 并具有 Add 方法的类型。 除了具体类型外,还可以使用接口 System.Collections.Generic.IEnumerable<T>System.Collections.Generic.IReadOnlyCollection<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IList<T>

使用接口类型时,编译器会为所提供的参数合成存储。 可以在 params 集合的功能规范中了解更多信息。

例如,方法声明可以将跨度声明为 params 参数:

public void Concat<T>(params ReadOnlySpan<T> items)
{
    for (int i = 0; i < items.Length; i++)
    {
        Console.Write(items[i]);
        Console.Write(" ");
    }
    Console.WriteLine();
}

新锁定对象

.NET 9 运行时包含一种新的线程同步类型,即 System.Threading.Lock 类型。 该类型通过其 API 提供更好的线程同步。 Lock.EnterScope() 方法进入独占范围。 从中返回的 ref struct 支持退出独占范围的 Dispose() 模式。

C# lock 语句可识别锁定的目标是否为 Lock 对象。 如果是,则使用更新的 API,而不是使用 System.Threading.Monitor 的传统 API。 如果将 Lock 对象转换为另一种类型,编译器也会识别,并生成基于 Monitor 的代码。 可以在新锁定对象的功能规范中了解详细信息。

通过此功能,您可以通过更改对象 lock的类型来获得新库类型的优势。 其他代码无需更改。

新的转义序列

你可以使用 \e 作为 ESCAPE 字符 Unicode U+001B字符文本转义序列。 以前,你使用的是 \u001b\x1b。 不建议使用 \x1b,因为如果 1b 后面的下一个字符是有效的十六进制数字,则那些字符会成为转义序列的一部分。

方法组自然类型

此功能对涉及方法组的重载解析进行了少量优化。 方法组是一个方法,并且所有重载都具有相同的名称。 编译器以前的行为是为方法组构造完整的候选方法集。 如果需要自然类型,则自然类型是根据整套候选方法确定的。

新行为是在每个作用域内削减候选方法集,移除那些不适用的候选方法。 通常,被删除的方法是具有错误参数或不满足约束的泛型方法。 只有在没有找到候选方法的情况下,该过程才会继续到下一个外部作用域。 此过程更紧密地遵循重载决策的一般算法。 如果在给定作用域内找到的所有候选方法都不匹配,则该方法组没有自然类型。

您可以在 提案规范中查看更改的详细信息。

隐式索引访问

现在在对象初始化表达式中允许使用隐式“从末尾”索引运算符^。 例如,你现在可以在对象初始值设定项中初始化数组,如以下代码所示:

public class TimerRemaining
{
    public int[] buffer { get; set; } = new int[10];
}

var countdown = new TimerRemaining()
{
    buffer =
    {
        [^1] = 0,
        [^2] = 1,
        [^3] = 2,
        [^4] = 3,
        [^5] = 4,
        [^6] = 5,
        [^7] = 6,
        [^8] = 7,
        [^9] = 8,
        [^10] = 9
    }
};

TimerRemaining 类包括一个 buffer 数组,其初始化长度为 10。 前面的示例使用“从末尾”索引操作符 (^) 为数组赋值,从而有效地创建了一个从 9 开始倒数到 0 的数组。

在 C# 13 之前的版本中,对象初始化器中不能使用 ^ 运算符。 你需要从前面开始为元素编制索引。

迭代器和 async 方法中的 refunsafe

该功能和以下两个功能使 ref struct 类型可以使用新的结构体。 除非你编写自己的 ref struct 类型,否则你不会使用这些类型。 你更可能会看到间接的好处,因为 System.Span<T>System.ReadOnlySpan<T> 获得了更多的功能。

在 C# 13 之前,迭代器方法(使用 yield return 的方法)和 async 方法不能声明局部 ref 变量,也不能使用 unsafe 上下文。

在 C# 13 中,async 方法可以声明 ref 局部变量或 ref struct 类型的局部变量。 但是,无法跨 await 边界访问这些变量。 也不能跨越 yield return 边界访问它们。

这种放宽的限制使编译器可以在更多地方安全地使用 ref 本地变量和 ref struct 类型,并可验证其安全性。 可以在这些方法中安全地使用 System.ReadOnlySpan<T> 等类型。 如果违反了安全规则,编译器会告诉你。

同样,C# 13 允许在迭代器方法中使用 unsafe 上下文。 但是,所有 yield returnyield break 语句都必须在安全的上下文中。

allows ref struct

在 C# 13 之前,ref struct 类型不能声明为泛型或方法的类型参数。 现在,泛型类型声明可以添加反约束 allows ref struct。 此反约束声明为该类型参数提供的类型参数可以是 ref struct 类型。 编译器会对该类型参数的所有实例执行 ref 安全规则。

例如,可以像下面的代码一样声明一个泛型类型:

public class C<T> where T : allows ref struct
{
    // Use T as a ref struct:
    public void M(scoped T p)
    {
        // The parameter p must follow ref safety rules
    }
}

这样,System.Span<T>System.ReadOnlySpan<T> 等类型就可以与泛型算法一起使用(如果适用)。 可以在 where 的更新以及有关泛型约束的编程指南文章中了解详细信息。

ref struct 接口

在 C# 13 之前,ref struct 类型不允许实现接口。 从 C# 13 开始,这些功能可以实现。 你可以声明 ref struct 类型实现某个接口。 但是,为了确保 ref 安全规则,ref struct 类型无法转换为接口类型。 这种转换是装箱转换,可能会违反 ref 安全性。 ref struct 中的显式接口方法声明只能通过类型参数访问,而该类型参数为 allows ref struct。 此外,ref struct 类型必须实现接口中声明的所有方法,包括默认实现的方法。

有关 ref struct 类型 和新增 allows ref struct 泛型约束的更多信息,请参阅更新。

更多部分成员

可以在 C# 13 中声明 partial 属性和 partial 索引器。 部分属性和索引器通常遵循与 partial 方法相同的规则:创建一个 声明声明,一个实现声明。 这两种声明的签名必须匹配。 一个限制是,不能使用自动属性声明来实现部分属性。 未声明正文的属性被视为声明声明

public partial class C
{
    // Declaring declaration
    public partial string Name { get; set; }
}

public partial class C
{
    // implementation declaration:
    private string _name;
    public partial string Name
    {
        get => _name;
        set => _name = value;
    }
}

可以在有关部分成员的文章中了解详细信息。

重载解析优先级

在 C# 13 中,编译器识别 OverloadResolutionPriorityAttribute,以便优先选择一个重载而不是另一个。 库作者可以使用该属性确保新的、更好的重载比现有的重载更受青睐。 例如,可以添加性能更高的新重载。 你不想中断使用库的现有代码,但又希望用户在重新编译时更新到新版本。 可以使用 重载解析优先级 来指示编译器应该优先选择哪个重载。 优先考虑优先级最高的超载。

此功能的目的是让库作者在添加新的重载时避免出现歧义。 库作者应谨慎使用该属性,以避免混淆。

field 关键字

field 上下文关键字在 C# 13 中作为预览特性提供。 令牌 field 访问属性访问器中的编译器合成支持字段。 它使你可以编写访问器的主体,而无需在类型声明中显式声明备份字段。 可以为字段支持的属性的一个或两个访问器声明一个主体。

field 功能作为预览功能发布。 我们希望从你的使用经验中学习。 如果类型中还包含一个名为 field 的字段,则在读取代码时可能会出现重大更改或混淆。 可以使用 @fieldthis.field 来消除 field 关键字和标识符之间的歧义。

重要

field 关键字是 C# 13 中的一项预览功能。 必须使用 .NET 9,并在项目文件中将 <LangVersion> 元素设置为 preview,才能使用 field 上下文关键字。

在有名为 field 的字段的类中使用 field 关键字功能时应小心谨慎。 新的 field 关键字在属性访问器的范围内隐藏一个名为 field 的字段。 可以更改 field 变量的名称,或者使用 @ 令牌将 field 标识引用为 @field。 可以通过阅读 field关键字的功能规范来了解详细信息。

如果试用了此功能并有反馈,请将其添加到 存储库中的 csharplang

另请参阅