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)该表达式为单个表达式建立不安全的上下文。 当语法上不能出现 catch 块时,这会很有用,例如在字段初始值设定项、构造函数初始值设定项或 unsafe 筛选器中:
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 成员和显式布局字段上的修饰符。 但是,requires-unsafe 成员模型以及程序集选择启用更新后的内存安全规则的机制尚未可用,因此 safe 和 unsafe 目前对调用方不起作用。
有关详细信息,请参阅语言引用或功能规范中的不安全代码、指针类型和函数指针。