首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >开始使用 C# 14

开始使用 C# 14

作者头像
JusterZhu
发布2025-11-24 13:42:17
发布2025-11-24 13:42:17
1070
举报
文章被收录于专栏:JusterZhuJusterZhu

C# 14 介绍

Bill Wagner

C#/.NET 首席内容开发者

C# 14 与 .NET 10 一起发布。最引人注目的亮点是新的 extension 成员,但还有更多功能可以让您作为开发者的工作更高效。而且,我们添加了新功能,这些功能使 .NET 10 中的一些性能改进成为可能。请继续阅读,了解所有新功能,并找到链接以深入了解并立即开始使用这些功能。

扩展成员

扩展成员[1] 是 C# 14 的头条功能。新语法与现有的扩展方法完全兼容。扩展成员支持扩展属性、扩展运算符和静态扩展成员。

以下代码展示了扩展块的示例。扩展块包含两个实例扩展,后跟同一类型的两个静态扩展。接收器名称 source 在扩展仅包含静态扩展时是可选的。

代码语言:javascript
复制
public static class EnumerableExtensions
{
 // 实例式扩展成员:'source' 是接收器变量
 extension<TSource>(IEnumerable<TSource> source)
 {
  // 扩展属性
  public bool IsEmpty => !source.Any();

  // 扩展方法(为简洁起见省略主体)
  public IEnumerable<TSource> Where(Func<TSource, bool> predicate)
  {
   // 实现将过滤 'source'
   throw new NotImplementedException();
  }

  // 静态扩展属性
  public static IEnumerable<TSource> Identity => Enumerable.Empty<TSource>();

  // 作为扩展提供的静态用户定义运算符
  public static IEnumerable<TSource> operator +(
   IEnumerable<TSource> left,
   IEnumerable<TSource> right) => left.Concat(right);
 }
}

使用示例:

代码语言:javascript
复制
int[] data = ...;
// 访问实例扩展属性:
if (data.IsEmpty) { /* ... */ }

// 访问静态扩展运算符 +
var combined = data + [ 4, 5 ];

// 访问静态扩展属性:
var empty = IEnumerable<int>.Identity;

因为扩展块与现有的扩展方法在源代码和二进制上兼容,您可以一次迁移一个方法。依赖的程序集无需重新编译,并继续绑定到原始符号。

您可以在 C# 指南[2] 和 extension 关键字文章[3] 中了解更多并探索扩展成员。您还可以阅读 扩展提案[4] 中的功能设计细节。

为您带来更多生产力

这组语言功能有一个共同目标:减少日常任务的语法摩擦,让您专注于领域逻辑而不是繁文缛节。它们消除了样板代码,移除了常见的条件块,简化了 lambda 声明,增强了源生成器的部分类型,并使 nameof 在泛型场景中更具表现力。单独来看,每个功能节省几行代码和更多输入。合起来,它们转化为更干净的代码、更少的琐碎标识符,以及更清晰地传达意图的代码。

field 关键字

大多数属性从简单的自动实现属性开始。后来您发现需要添加少量逻辑——空值合并、钳位、简单规范化或引发防护——仅在单个访问器上。在 C# 14 之前,这种需求迫使您转换为完全手写的后备字段模式:

代码语言:javascript
复制
// 之前
private string _message = "";
public string Message
{
 get => _message;
 init => _message = value 
           ?? throw new ArgumentNullException(nameof(value));
}

上下文关键字 field 在演化路径上创建了一个中间步骤:保持自动属性的简洁性,仅在需要的地方注入最小逻辑,并让编译器合成并命名后备存储。您只需添加需要逻辑的访问器主体,并通过 field 引用编译器生成的存储:

代码语言:javascript
复制
// 之后 (C# 14)
public string Message
{
 get; // 自动 get
 init => field = value 
           ?? throw new ArgumentNullException(nameof(value));
}

这是自动实现和完全手写属性之间的桥梁:从 public string Message { get; init; } 开始,然后当您需要快速防护时,仅转换需要代码的访问器,并使用 field 而不是引入私有成员并复制琐碎的 getter。此模式在许多属性各需要一行检查时可扩展——您的类保持视觉轻量级,差异保持小。field 的另一个优势是避免创建新的命名私有字段。类型中的所有代码必须使用属性来访问或修改属性的值。

此功能在 .NET 9 中作为预览可用。field 上下文关键字[5] 现在在 C# 14 中正式可用(参见 新功能[6])。

非绑定泛型类型和 nameof

以前,要使用仅泛型类型名称进行日志记录或抛出异常,您要么硬编码字符串,要么使用封闭构造类型:

代码语言:javascript
复制
// 之前
var listTypeName = nameof(List<int>); // "List"
// 或者:
const string Expected = "List";

现在 nameof 接受非绑定泛型类型。此功能消除了仅为检索泛型类型名称而选择任意类型参数的需求:

代码语言:javascript
复制
// 之后 (C# 14)
var listTypeName = nameof(List<>); // "List"

这会生成泛型类型名称一次,而不暗示任何特定实例化。请参阅 nameof 运算符参考[7] 以了解更多。

带有修饰符的简单 lambda 参数

在早期版本中,委托中的参数修饰符如 out 需要所有参数的完整类型注解:

代码语言:javascript
复制
// 之前
delegate bool TryParse<T>(string text, out T value);
TryParse<int> parse = (string text, out int result) => int.TryParse(text, out result);

现在,您可以保持简洁的隐式类型形式,同时在一或多个参数上使用 outrefinscoped 等修饰符:

代码语言:javascript
复制
// 之后 (C# 14)
TryParse<int> parse = (text, out result) => int.TryParse(text, out result);

参数类型仍然被推断,保留了 lambda 表达式的简洁语法。请参阅 C# 语言参考中关于 lambda 表达式参数修饰符[8] 的部分。它保持 lambda 的简洁性,同时暴露数据流的语义(outrefinscoped)。

空值条件赋值

受保护的赋值以前需要显式空值检查:

代码语言:javascript
复制
// 之前
if (customer is not null)
{
 customer.Order = CreateOrder();
 customer.Total += CalculateIncrement();
}

现在,您可以使用赋值左侧的空值条件运算符直接赋值(并使用复合赋值)。仅当赋值的接收器不为 null 时,才评估右侧:

代码语言:javascript
复制
// 之后 (C# 14)
customer?.Order = CreateOrder();
customer?.Total += CalculateIncrement();

这减少了缩进,并视觉上凸显了重要工作。此功能直接与现有的 空值条件运算符[9] 集成,因此它们可以出现在赋值的左侧。它仅在接收器不为 null 时评估右侧表达式,避免了辅助局部变量或重复检查。请参阅 空值条件赋值[10] 和 功能规范[11]。

部分事件和构造函数

大型生成或源生成的部分类型现在可以将事件和构造函数逻辑分布到文件中,从而使生成器或不同文件能够清晰地贡献:

代码语言:javascript
复制
public partial class Widget(int size, string name) // 主构造函数的定义声明
{
 public partial event EventHandler Changed; // 声明事件声明(类似于字段)
}

public partial class Widget
{
 public partial event EventHandler Changed // 事件的定义声明。
 {
  add => _changed += value;
  remove => _changed -= value;
 }

 private EventHandler? _changed;

 // 实现声明可以添加构造函数主体逻辑
 public Widget
 {
  Initialize();
 }
}

这种分离启用了新的源生成场景(例如,生成器提供定义成员,用户代码提供行为,或反之)。它简化了手动编写的逻辑。它保持更专注于您手动编写的算法。

请参阅编程指南中的 部分构造函数[12] 和 部分成员参考[13] 以获取语法细节。

为您的用户带来更多性能

升级到 .NET 10 后,您将看到许多原始吞吐量优势来自于运行时和 BCL 采用新的 C# 14 功能。核心库已经使用这些功能,因此即使您从未编写此语法,您的应用程序通常也会更快。.NET 10 性能改进帖子[14] 突出了受益于 Span 密集型解析、UTF-8 处理和数字例程的部分。特别是两个语言添加解锁了更干净、更快的库实现:隐式 span 转换[15] 和 用户定义复合赋值[16]。

隐式 span 转换

Span<T> / ReadOnlySpan<T> 是无分配 API 的核心。C# 14 添加了数组、Span 和 ReadOnlySpan 之间的隐式转换,因此您编写更少的仪式代码,JIT 看到更简单的调用图。这转化为更少的临时变量、更少的边界检查,以及框架中更激进的内联(如性能博客中描述的文本和解析微基准部分)。

早期 C# 版本需要如下代码:

代码语言:javascript
复制
// 之前
string line = ReadLine();
ReadOnlySpan<char> key = line.AsSpan(0, 5); // 显式 AsSpan
ProcessKey(key);

int[] buffer = GetBuffer();
Span<int> head = new(buffer, 0, 8); // 显式 Span 构造函数
Accumulate(head);

现在,您可以编写如下代码:

代码语言:javascript
复制
// 之后 (C# 14)
string line = ReadLine();
ProcessKey(line[..5]);              // 子字符串切片隐式转换

int[] buffer = GetBuffer();
Accumulate(buffer[..8]);

库作者利用这些转换来移除辅助局部变量,并内联表达切片意图。优势包括更少的显式 AsSpan 或构造函数调用、更清晰的切片意图(鼓励 span 友好的重载),以及通过更广泛的无分配路径减少分配的框架优化。请阅读 First-Class Span 类型规范[17] 以了解更多。

用户定义复合赋值

高性能数字和向量类型通常在紧凑循环中累积值。没有专用的复合赋值运算符,代码要么重复左侧引用,要么通过普通二元运算符创建中间临时变量——两种模式都可能抑制某些 JIT 优化。C# 14 允许您显式声明复合赋值运算符(+=-= 等),因此编译器直接分派到您的实现。利用此功能的库(例如,性能博客中引用的 SIMD 友好的帮助方法)来避免额外临时变量,并暴露更惯用的 API。

而不是这样:

代码语言:javascript
复制
// 之前
BigVector sum = BigVector.Zero;
foreach (var v in values)
{
 sum = sum.Add(v); // 每次迭代中间结果
}

在您提供可以就地更新结果的复合运算符后:

代码语言:javascript
复制
// 之后 (C# 14)
BigVector sum = BigVector.Zero;
foreach (var v in values)
{
 sum += v; // 直接调用用户定义的运算符 +=
}

定义二元和复合运算符:

代码语言:javascript
复制
public struct BigVector(float x, float y, float z)
{
 public float X { get; private set => value = field; } = x;
 public float Y { get; private set => value = field; } = y;
 public float Z { get; private set => value = field; } = z;

 public static BigVector operator +(BigVector l, BigVector r)
  => new(l.X + r.X, l.Y + r.Y, l.Z + r.Z);

 public void operator +=(BigVector r)
 {
  X += r.X;
  Y += r.Y;
  Z += r.Z;
 }
}

细节出现在 C# 指南中的 运算符重载文章[18] 和 复合赋值规范[19] 中。您还应咨询 编译器破坏性变更文章[20] 以了解潜在问题。您可能遇到与 Enumerable.Reverse[21] 相关的问题。

总结

这是我们 在 C# 14 中交付的内容[22] 的快速导览:新的扩展、使您更高效的几项功能,以及使您的 C# 程序性能更好的增强。下载 .NET 10 并在您的应用程序上试用它。参与持续讨论,以继续使 C# 成为您的优秀语言选择。

引用链接

[1] 扩展成员: https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14#extension-members [2] C# 指南: https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/extension-methods [3] extension 关键字文章: https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/extension [4] 扩展提案: https://learn.microsoft.com/dotnet/csharp/language-reference/proposals/csharp-14.0/extensions [5] field 上下文关键字: https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/field [6] 新功能: https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14#the-field-keyword [7] nameof 运算符参考: https://learn.microsoft.com/dotnet/csharp/language-reference/operators/nameof [8] lambda 表达式参数修饰符: https://learn.microsoft.com/dotnet/csharp/language-reference/operators/lambda-expressions#input-parameters-of-a-lambda-expression [9] 空值条件运算符: https://learn.microsoft.com/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and- [10] 空值条件赋值: https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14#null-conditional-assignment [11] 功能规范: https://learn.microsoft.com/dotnet/csharp/language-reference/proposals/csharp-14.0/null-conditional-assignment [12] 部分构造函数: https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/constructors#partial-constructors [13] 部分成员参考: https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/partial-member [14] .NET 10 性能改进帖子: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-10/ [15] 隐式 span 转换: https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14#implicit-span-conversions [16] 用户定义复合赋值: https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14#user-defined-compound-assignment [17] 一流 span 类型规范: https://learn.microsoft.com/dotnet/csharp/language-reference/proposals/csharp-14.0/first-class-span-types [18] 运算符重载文章: https://learn.microsoft.com/dotnet/csharp/language-reference/operators/operator-overloading [19] 复合赋值规范: https://learn.microsoft.com/dotnet/csharp/language-reference/proposals/csharp-14.0/user-defined-compound-assignment [20] 编译器破坏性变更文章: https://learn.microsoft.com/dotnet/csharp/whats-new/breaking-changes/compiler%20breaking%20changes%20-%20dotnet%2010 [21] Enumerable.Reverse: https://learn.microsoft.com/dotnet/csharp/whats-new/breaking-changes/compiler%20breaking%20changes%20-%20dotnet%2010#enumerablereverse [22] 在 C# 14 中交付的内容: https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JusterZhu 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C# 14 介绍
    • 扩展成员
    • 为您带来更多生产力
      • field 关键字
      • 非绑定泛型类型和 nameof
      • 带有修饰符的简单 lambda 参数
      • 空值条件赋值
      • 部分事件和构造函数
    • 为您的用户带来更多性能
      • 隐式 span 转换
      • 用户定义复合赋值
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档