C# 15 中的新增功能

C# 15 包含以下新功能。 使用最新的 Visual Studio 2026 内部预览版或 .NET 11 预览版 SDK 试用这些功能:

C# 15 是最新的 C# 预览版。 .NET 11 预览版支持 C# 15。 有关详细信息,请参阅 C# 语言版本控制。

可以从 .NET 下载页下载最新的 .NET 11 预览版 SDK。 还可以下载 Visual Studio 2026 内部预览版,其中包括 .NET 11 预览版 SDK。

“C# 中的新增功能”页面在公共预览版中可用时会添加新功能。 roslyn 功能状态页的工作集部分跟踪即将推出的功能何时合并到主分支中。

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

注释

我们对这些功能的反馈感兴趣。 如果发现上述任何新功能的问题,请在 dotnet/roslyn 存储库中创建一个新问题。

集合表达式参数

可以用 with(...) 元素作为集合表达式的第一个元素,将参数传递给底层集合的构造函数或工厂方法。 借助此功能,可以直接在集合表达式语法中指定容量、比较器或其他构造函数参数。

以下示例演示如何将容量参数传递给List<T>构造函数,以及传递比较器给HashSet<T>:

string[] values = ["one", "two", "three"];

// Pass capacity argument to List<T> constructor
List<string> names = [with(capacity: values.Length * 2), .. values];

// Pass comparer argument to HashSet<T> constructor
HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
// set contains only one element because all strings are equal with OrdinalIgnoreCase

若要了解有关集合表达式参数的详细信息,请参阅有关集合表达式或特征规范的语言参考文章。 有关在集合初始值设定项中使用集合表达式参数的信息,请参阅 对象和集合初始值设定项。

联合类型

C# 15 引入了 联合类型,这些类型表示可以是多种 事例类型之一的值。 使用 union 关键字声明联合:

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

public union Pet(Cat, Dog, Bird);

联合提供了从每种用例类型的隐式转换,并且编译器确保 switch 表达式在所有案例类型上是穷尽的:

Pet pet = new Dog("Rex");

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
};

运行时包括从 .NET 11 预览版 5 开始的 UnionAttribute 和 IUnion 类型。 建议规范中的某些功能尚未实现。 这些功能将在未来预览版中推出。

有关详细信息,请参阅语言参考或功能规范中的联合类型。

封闭层次结构

从 C# 15 开始,可以将修饰符应用于 closed 类来声明 封闭层次结构。 封闭类只能在其声明程序集内被派生,这会在编译时固定直接后代的集合:

public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;

由于编译器知道每个直接后代,因此 switch 处理每个子代的表达式是详尽的,不需要默认 arm:

string Describe(GateState state) => state switch
{
    Closed => "closed",
    Open(var percent) => $"{percent}% open",
    // No warning: every direct descendant of 'GateState' is handled.
};

closed 修饰符是上下文关键字。 closed 类是隐式 abstract 的,不能与 sealed、static 或显式 abstract 修饰符结合使用。 派生不是传递性的:封闭类的非封闭后代仍然可以在其他程序集中被派生。 为了将穷举性检查扩展到层次结构下游,也要将中间后代标记为 closed。

有关详细信息,请参阅语言参考中的 封闭修饰符 和 封闭层次结构模式 ,或 功能规范。

扩展索引器

从 C# 15 开始,可以在 块中声明 extension。 借助扩展索引器,可以对接收器编制索引,如同针对接收器类型声明索引器一样。 由于索引器始终是实例成员,因此声明索引器的扩展块必须提供命名接收器参数。

以下示例在 IEnumerable<int> 上声明了一个只读索引器,该索引器返回指定位置处的元素:

public static class SequenceIndexer
{
    extension(IEnumerable<int> sequence)
    {
        public int this[int index] => sequence.ElementAt(index);
    }
}

如同将索引器视为接收器类型的成员一样,对接收器编制索引:

IEnumerable<int> numbers = Enumerable.Range(1, 10);
int third = numbers[2];

有关详细信息,请参阅语言参考或功能规范中的扩展声明。

标为 break 和 continue

从 C# 15 开始, break 语句 continue 可以在封闭构造上命名标签。 使用标记 break 退出封闭循环或 switch 语句。 使用标记 continue 启动封闭循环的下一次迭代。

带标签的 break 和 continue 取代以往用于引导控制流穿过嵌套循环的变通方法,例如在内层循环中设置布尔标志并在每个外层循环中进行检查,或者使用 goto 跳过这些循环。 直接在跳转语句中指明目标循环,可以省去这些额外的记录工作,并使预期的控制流程更易于理解。

outer: for (int row = 0; row < grid.Height; row++)
{
    for (int column = 0; column < grid.Width; column++)
    {
        if (grid[row, column].IsBlocked)
        {
            continue outer;
        }

        if (grid[row, column].IsGoal)
        {
            break outer;
        }
    }
}

将标签直接放置在它标识的循环或 switch 语句上。 如果没有标签,break 和 continue 会保持原有行为,并作用于最内层的适用语句。

IDE0410 样式规则会标识可由带标签的跳转语句替换的布尔标志和 goto 模式,并显示每种情况的前后对比例子。

有关详细信息,请参阅语言参考或功能规范中的 Jump 语句。

内存安全

C# 15 开启了一项跨多个版本重新定义该语言内存安全性的工作。 目标是将 unsafe 上下文与实际访问非托管内存的操作关联起来,而不是与指针类型的存在关联起来。 大多数内存安全漏洞都来自这些访问操作,因此该语言使审阅者和审核员脱颖而出。

启用更新后的规则时,成员上的 unsafe 会将其标记为 requires-unsafe:审查义务会转移给调用方,调用方必须在 unsafe 上下文中使用该成员。 编译器使用 System.Runtime.CompilerServices.MemorySafetyRulesAttribute 特性记录程序集对已更新规则的使用情况。 该模型还添加了一个 safe 上下文关键字,用于将显式或扩展布局类型中的成员和字段标记为 extern 安全。 这些规则共同使潜在的内存不安全性边界跨程序明确。

第一步包括指针放松。 使用 preview 语言版本进行编译时,以下操作不再需要 unsafe 上下文:

  • 声明指针类型并使用运算符获取变量 & 的地址。
  • fixed 语句,用于锁定变量。
  • 将 stackalloc 表达式转换为指针。
  • 应用于任何非托管类型的 sizeof 运算符。

以下示例创建并固定没有上下文的 unsafe 指针:

int number = 42;
int* pointer = &number;

int[] numbers = [10, 20, 30];
fixed (int* first = numbers)
{
    // Dereferencing the pointer still requires an unsafe context.
}

访问指向内存的操作(如指针间接访问(*p)、指针成员访问(p->member)、指针元素访问(p[i])和函数指针调用仍需要 unsafe 上下文。

C# 15 还添加一个 unsafe 表达式, unsafe(expression)该表达式为单个表达式建立不安全的上下文。 在语法层面不允许出现 unsafe 代码块时,它非常有用,例如字段初始化器、构造函数初始化器或 catch 筛选器:

class Header
{
    // A field initializer can't contain an unsafe block, but it can contain an unsafe expression.
    static readonly int Signature = unsafe(ReadSignature());

    static unsafe int ReadSignature()
    {
        int rawValue = 0x1234;
        int* pointer = &rawValue;
        return *pointer;
    }
}

与内存安全预览版的其余部分一样, unsafe 表达式需要 preview 语言版本和 AllowUnsafeBlocks 编译器选项。

编译器还会将上下文关键字 safe 识别为显式布局类型或扩展布局类型中 extern 成员和字段的修饰符。 将 preview 设置为 LangVersion,以启用新的语法和指针宽松处理。 若要启用更新的规则(包括 需要不安全 的调用方义务)请启用 updated-memory-safety-rules 编译器功能。 有关基于项目和文件的程序语法,请参阅 “启用更新的内存安全规则”。

有关详细信息,请参阅语言引用或功能规范中的不安全代码、指针类型和函数指针。

另见