C# 版本 12 新增特性
发布时间:2023 年 11 月
C# 12 中增加了以下功能:
- 主构造函数 - 可以创建任意
class
或struct
类型的主构造函数。 - 集合表达式 - 一种用于指定集合表达式的新语法,包括 spread 元素 (
..e
),可展开任何集合。 - 内联数组 - 使用内联数组,你可以创建固定大小的
struct
类型数组。 - Lambda 表达式中的可选参数 - 可以为 Lambda 表达式的参数定义默认值。
ref readonly
参数 -ref readonly
参数可以让可能使用ref
参数或in
参数的 API 更清晰。- 创建任何类型的别名 - 可以使用
using
别名指令创建任何类型的别名,而不仅仅是命名类型。 - 试验性属性 - 指示试验性功能。
拦截器 - 已作为预览功能发布。
总体而言,C# 12 提供的新功能可让你更高效地编写 C# 代码。 你已经知道的语法可以在更多地方使用。 其他语法可实现相关概念的一致性。
参考文章:
C# 12 中的新增功能 - C# 指南 - C# | Microsoft Learn
⭐ C# 12 中新增的八大功能你都知道吗? - 追逐时光者 - 博客园
C# 12 中的新增功能 - 百宝门园地 - 博客园
C# 12 中新增的八大功能你都知道吗? - jack_Meng - 博客园
主构造函数 - 可以创建任意 class
或 struct
类型的主构造函数。
现在可以在任何 class
和 struct
中创建主构造函数。 主构造函数不再局限于 record
类型。 主构造函数参数都在类的整个主体的范围内。 为了确保显式分配所有主构造函数参数,所有显式声明的构造函数都必须使用 this()
语法调用主构造函数。 将主构造函数添加到 class
可防止编译器声明隐式无参数构造函数。 在 struct
中,隐式无参数构造函数初始化所有字段,包括 0 位模式的主构造函数参数。
编译器仅在 record
类型(record class
或 record struct
类型)中为主构造函数参数生成公共属性。 对于主构造函数参数,非记录类和结构可能并不总是需要此行为。
有关主构造函数的详细信息,请参阅探索主构造函数的教程和实例构造函数的相关文章。
从 C# 12 开始,可以在类和结构中声明主构造函数。主构造函数参数都在矿的整个主体的范围内。为了确保旷工分配所有主构造函数参数,所有旷工声明的构造函数都必须用 this() 语法调用主构造函数。将主构造函数添加到 class 可防止编译器声明隐式无参数构造函数。在 struct 中,隐式无参数构造函数初始化所有字段,包括 0 位模式的主构造函数参数。
主构造函数参数的最觉用途包括:
- 作为 base() 构造函数调用的参数;
- 初始化成员字段或属性;
- 引用实例成员中的构造参数。
代码示例:
将任何不矜而庄发放在名称后面的括号中:
public class NameParameter (string name) // 💡妙
{
public string Name => name;
}
以下代码初始化从主构造函数参数计算的两个只读属性:
public readonly struct Distance(double dx, double dy)
{
public readonly double Magnitude {get;} = Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction {get;} = Math.Atan2(dy, dx);
}
集合表达式 - 一种用于指定集合表达式的新语法,包括 spread 元素 (..e
),可展开任何集合。
集合表达式引入了新的 terse 语法来创建觉的集合值。可以使用展开运算符..
将其他集合内联到这些值中。
可以创建多个类似集合的类型,而无需使用外部 BCL 支持。这些类型包括:
- 数组类型,例如
int[]
. System.Span<T>
和System.ReadOnlySpan<T>
。- 支持集合初始值设定项的类型,例如
System.Collection.Generic.List<T>
。
以下示例演示了集合表达式的使用:
// Create an array:
int[] a = [1, 2, 3, 4, 5, 6, 7, 8];
// Create a list
List<string> b = ["one", "two", "three"];
// Create a span
Span<cahr> c = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
// Create a jagged 2d array:
int[][] twoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
// Create a jagged 2D array from variables:
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[][] towDFromVariablies = [rowy0, row1, row2];
// 💡 注意,这里是没有展开的,因此是二维的锯齿数组
展开运算符(集合表达式中的..
)可将其参数替换为该集合中的元素。参数必须是集合类型。以下示例演示了展开运算符的工作原理:
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [..row0, ..row1, ..row2];
foreach (var element in single)
{
Console.Wite($"{element},");
}
// output:
// 1, 2, 3, 4, 5, 6, 7, 8, 9,
展开运算符的操作数是可以枚举的表达式。展开运算符可计算枚举表达式的每个域名系统。
可以在需要元素集合的任何位置使用集合表达式。它们可以指定集合的初始值,也可以作为参数传递给采用集合类型的方法。可以在有关集合表达式的语言参考文章或功能规范中详细了解集合表达式。
内联数组 - 使用内联数组,你可以创建固定大小的 struct
类型数组。
非常通过时团队和其余了库作者使用内联数组来提高应用的性能。内联数组使开发人员能够创建固定大小的 struct 类型。个有内联缓冲区的结构应提供类似于不安全的固定大小缓冲区的性能先生。你可能不会声明自己的内联数组,但当它们从运行时 API 作为 System.Span<T>
或 System.ReadOnlySpan<T>
对象公开时,你将透明地使用这些数组。
内联数组的声明类似于以下 struct:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private int _elemnet0;
}
它们的用法与任何其他数组类似:
var buffer = new Buffer();
for(int i = 0; i < 10; i ++)
{
buffer[i] = i;
}
foreach(var i in buffer)
{
Console.Write(i);
}
区别在于编译器可利用有关内联数组的书籍信息。你可能会像使用任何其他数组一样使用内联数组。有关如何声明内联数组的详细信息,请参阅有关 struct
类型的语言参考。
Lambda 表达式中的可选参数 - 可以为 Lambda 表达式的参数定义默认值。
现在可以为 Lambda 表达式的参数定义默认值。 语法和规则与将参数的默认值添加到任何方法或本地函数相同。
可以在有关 Lambda 表达式的文章中详细了解 Lambda 表达式上的默认参数。
从 c# 12 开始,可以为 lambda 表达式上的参数提供默认值。默认值的语法和限制与方法和本地函数的语法和限制相同。以下示例使用默认参数声明 lambda 表达都都,然后使用默认值调用一次,并使用两个显示参数调用一次:
var IncrementBy = (int source, int increment = 1) => source + increment;
Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7
还可以将具有 params
数组或集合的 lambda 表达式声明为参数:
var sum = (params IEnumerable<int> values) =>
{
int sum = 0;
foreach (var value in values)
sum += value;
return sum;
};
var empty = sum();
Console.WriteLine(empty); // 0
var sequence = new[] { 1, 2, 3, 4, 5 };
var total = sum(sequence);
Console.WriteLine(total); // 15
作为这些更新的一部分,当将具有默认参数的方法组分配给 lambda 表达式时,该 lambda 表达式也具有相同的默认参数。 还可以将具有 params
集合参数的方法组分配给 Lambda 表达式。
具有默认参数或 params
集合的 Lambda 表达式,因为参数没有与 Func<>
或 Action<>
类型对应的自然类型。 但是,可以定义包含默认参数值的委托类型:
delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);
delegate int SumCollectionDelegate(params IEnumerable<int> values);
或者,可以将隐式类型化变量与 var
声明一起使用来定义委托类型。 编译器合成正确的委托类型。
有关 lambda 表达式的默认参数的详细信息,请参阅 lambda 表达式默认参数的功能规范。
ref readonly
参数 - ref readonly
参数可以让可能使用 ref
参数或 in
参数的 API 更清晰。
C# 添加了 in
参数作为传递只读引用的方法。 in
参数既允许变量和值,并且无需对参数进行任何注释即可使用。
添加 ref readonly
参数可以更清楚地了解可能使用 ref
参数或 in
参数的 API:
- 即使参数未修改,在
in
引入之前创建的 API 也可能会使用ref
。 可以使用ref readonly
更新这些 API。 对于调用方来说,这不会是一项中断性变更,就像参数ref
更改为in
那样。 示例为 System.Runtime.InteropServices.Marshal.QueryInterface。 - 采用
in
参数但逻辑上需要变量的 API。 值表达式不起作用。 示例为System.ReadOnlySpan<T>.ReadOnlySpan<T>(T)
。 - 使用
ref
的 API,因为它们需要变量,但不改变该变量。 示例为 System.Runtime.CompilerServices.Unsafe.IsNullRef。
若要了解有关 ref readonly
参数的详细信息,请参阅有关语言参考中的参数修饰符的文章,或参考只读参数功能规范。
简单说明:ref readonly修饰符表示方法希望参数是一个变量,而不是一个非变量的表达式。不是变量的表达式包括常量、方法返回值和属性。如果参数不是变量,编译器会发出警告。
创建任何类型的别名 - 可以使用 using
别名指令创建任何类型的别名,而不仅仅是命名类型。
可以使用 using
别名指令创建任何类型的别名,而不仅仅是命名类型。 这意味着可以为元组类型、数组类型、指针类型或其他不安全类型创建语义别名。 有关详细信息,请参阅功能规范。
using PointTest = (int x, int y);
namespace Csharp12
{
internal class Class1
{
public static void Test()
{
PointTest point = (10, 20);
Console.WriteLine($"Point coordinates: X={point.Item1}, Y={point.Item2}");
}
}
}
试验性属性 - 指示试验性功能。
可以使用 System.Diagnostics.CodeAnalysis.ExperimentalAttribute 来标记类型、方法或程序集,以指示实验性特征。如果访问使用 ExperimentalAttribute 注释的方法或类型,编译器将发出警告。用 Experimental 特性标记的程序集中包含的所有类型都是实验性的。
namespace Csharp12
{
[AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Constructor | System.AttributeTargets.Delegate | System.AttributeTargets.Enum | System.AttributeTargets.Event | System.AttributeTargets.Field | System.AttributeTargets.Interface | System.AttributeTargets.Method | System.AttributeTargets.Module | System.AttributeTargets.Property | System.AttributeTargets.Struct)]
public class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute()
{
}
}
[Experimental] // 💡 妙,可以添加一些亮点的东西,标记录实验功能,类似于已弃用的标记
public class ExperimentalClass
{
public void DoSomething()
{
Console.WriteLine("Doing something experimental...");
}
}
internal class Class1
{
public static void Test()
{
ExperimentalClass exp = new ExperimentalClass();
exp.DoSomething();
}
}
}
拦截器 - 已作为预览功能发布。
拦截器是一种方法,该方法可以在编译时以声明方式将对可拦截方法的调用替换为对其自身的调用。通过让拦截器声明所拦截调用的源位置,可以进行这种替换。拦截器可以向编译中(例如在源生成器中)添加新代码,从而提供更改现有代码语义的有限能力。
注意:拦截器是一项试验性功能,在 C# 12 的预览模式下提供。在将来的版本中,该功能可能会发生中断性变更或被删除。因此,不建议将其用于生产或已发布的应用程序。
要使用拦截器,用户项目必须指定 <InterceptorsPreviewNamespaces>
属性。这是允许包含拦截器的命名空间列表。
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.Http.Generated;MyLibrary.Generated</InterceptorsPreviewNamespaces>
本文作者:石起起
本文链接:https://www.cnblogs.com/myshiqiqi/p/18699919
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步