C# 10 中的新增功能

C# 10 向 C# 语言添加了以下功能和增强功能:

其他功能在预览模式下可用。 建议尝试使用这些功能并提供相关反馈。 它们可能会在最终发布之前发生更改。 为了使用这些功能,需要在项目中<LangVersion> 设置为 Preview。 阅读本文后面的关于泛型属性的信息。

.NET 6 支持 C# 10。 有关详细信息,请参阅 C# 语言版本控制

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

记录结构

可以使用 record structreadonly record struct 声明声明值类型记录。 现在,你可以通过 record class 声明阐明 record 是引用类型。

结构类型的改进

C# 10 引入了与结构类型相关的以下改进:

内插字符串处理程序

可以创建一种类型,该类型从内插字符串表达式生成结果字符串。 .NET 库在许多 API 中都使用此功能。 可以按照本教程生成一个内插字符串处理程序。

全局 using 指令

可将 global 修饰符添加到任何 using 指令,以指示编译器该指令适用于编译中的所有源文件。 这通常是项目中的所有源文件。

文件范围的命名空间声明

可使用 namespace 声明的新形式,声明所有后续声明都是已声明的命名空间的成员:

namespace MyNamespace;

这个新语法为最常见的 namespace 声明节省了水平和垂直空间。

扩展属性模式

从 C# 10 开始,可引用属性模式中嵌套的属性或字段。 例如,窗体的模式

{ Prop1.Prop2: pattern }

在 C# 10 及更高版本中有效,且其等效项

{ Prop1: { Prop2: pattern } }

在 C# 8.0 及更高版本中有效。

有关详细信息,请参阅扩展属性模式功能建议说明。 有关属性模式的详细信息,请参阅模式一文的属性模式部分。

Lambda 表达式改进

C# 10 包括了对 Lambda 表达式的处理方式的许多改进:

  • Lambda 表达式可以具有自然类型,这使编译器可从 Lambda 表达式或方法组推断委托类型。
  • 如果编译器无法推断返回类型,Lambda 表达式可以声明该类型。
  • 属性可应用于 Lambda 表达式。

这些功能使 Lambda 表达式更类似于方法和本地函数。 在不声明委托类型的变量的情况下,这些改进使得人们可以更容易使用 Lambda 表达式,并且它们可以与新的 ASP.NET Core 最小 API 更无缝地工作。

常数内插字符串

在 C# 10 中,如果所有占位符本身均为常量字符串,可以使用字符串内插来初始化 const 字符串。 在生成应用程序中使用的常量字符串时,字符串内插可以创建更多可读的常量字符串。 占位符表达式不能为数值常量,因为在运行时这些常量会转换为字符串。 当前区域性可能会影响其字符串表示形式。 有关详细信息,请参阅有关 const 表达式的语言参考信息。

记录类型可以密封 ToString

在 C# 10 中,在记录类型中重写 ToString 时可以添加 sealed 修饰符。 密封 ToString 方法可阻止编译器为任何派生的记录类型合成 ToString 方法。 sealed ToString 确保了所有派生记录类型都使用某个通用基记录类型中定义的 ToString 方法。 若要详细了解此功能,请阅读有关记录的文章。

在同一析构中进行赋值和声明

此更改取消了早期 C# 版本中的限制。 以前,析构可以将所有值赋给现有变量,或将新声明的变量初始化:

// Initialization:
(int x, int y) = point;

// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;

C# 10 取消了此限制:

int x = 0;
(x, int y) = point;

改进型明确赋值

在 C# 10 及更低版本中,在许多情况下,明确赋值和 Null 状态分析都会生成误报警告。 这些通常涉及与布尔常量的比较,仅在 if 语句中的 truefalse 语句以及 Null 合并表达式中使用变量。 这些示例会在早期版本的 C# 中生成警告,但在 C# 10 中不会:

string representation = "N/A";
if ((c != null && c.GetDependentValue(out object obj)) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ?.
if (c?.GetDependentValue(out object obj) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ??
if (c?.GetDependentValue(out object obj) ?? false)
{
   representation = obj.ToString(); // undesired error
}

此次改进的主要影响是,针对明确赋值和 Null 状态分析的警告更加准确。

允许在方法上使用 AsyncMethodBuilder 特性

在 C# 10 及更高版本中,除了可以为所有返回给定任务类型的方法指定方法生成器类型外,还可以为单个方法指定其他异步方法生成器。 自定义异步方法生成器可以实现高级的性能优化方案,其中给定的方法可受益于自定义生成器。

若要了解详细信息,请阅读有关编译器读取的属性的文章中有关 AsyncMethodBuilder 的部分。

CallerArgumentExpression 属性诊断

可以使用 System.Runtime.CompilerServices.CallerArgumentExpressionAttribute 来指定编译器用另一个实参的文本表示形式替换的形参。 此功能使库可以创建更具体的诊断。 以下代码测试条件。 如果条件为 false,则异常消息包含传递给 condition 的参数的文本表示形式:

public static void Validate(bool condition, [CallerArgumentExpression("condition")] string? message=null)
{
    if (!condition)
    {
        throw new InvalidOperationException($"Argument failed validation: <{message}>");
    }
}

可在语言参考部分的调用者信息属性一文中详细了解此功能。

增强型 #line pragma

C# 10 支持 #line pragma 的新格式。 你可能不会使用新格式,但你会看到它的作用。 这些增强功能支持使用 Razor 等域特定语言 (DSL) 实现更详细的输出。 Razor 引擎使用这些增强功能来改进调试体验。 你会发现调试器可以更准确地突出显示 Razor 源。 若要详细了解新语法,请参阅语言参考中有关预处理器指令的文章。 还可以阅读关于基于 Razor 的示例的功能规范

泛型属性

重要

泛型属性 是一种预览功能。 若要启用此功能,需要<LangVersion> 设置为 Preview。 此功能在最终发布之前可能会发生更改。

可以声明基类为 System.Attribute泛型类。 这为需要 System.Type 参数的属性提供了更方便的语法。 以前需要创建一个属性,该属性将 Type 作为其构造函数参数:

public class TypeAttribute : Attribute
{
   public TypeAttribute(Type t) => ParamType = t;

   public Type ParamType { get; }
}

并且为了应用该属性,需要使用 typeof 运算符:

[TypeAttribute(typeof(string))] 
public string Method() => default;

使用此新功能,可以改为创建泛型属性:

public class GenericAttribute<T> : Attribute { }

然后指定类型参数以使用该属性:

[GenericAttribute<string>()]
public string Method() => default;

可以应用全封闭构造的泛型属性。 换句话说,需要指定所有类型参数。 例如,不允许以下操作:

public class GenericType<T>
{
   [GenericAttribute<T>()] // Not allowed! generic attributes must be fully closed types.
   public string Method() => default;
}

类型参数必须满足与 typeof 运算符相同的限制。 不允许使用需要元数据注释的类型。 这方面的例子有:

  • dynamic
  • nint, nuint
  • string?(或任何可为 null 的引用类型)
  • (int X, int Y)(或使用 C# 元组语法的任何其他元组类型)。

这些类型不会直接在元数据中表示出来。 这些类型包括描述该类型的注释。 在所有情况下,都可以改为使用基础类型:

  • object(对于 dynamic)。
  • IntPtr 而不是 nintunint
  • string,而不是 string?
  • ValueTuple<int, int>,而不是 (int X, int Y)