新的 C# 12 功能在预览版中已经引入. 您可以使用最新的 Visual Studio 预览版或最新的 .NET 8 预览版 SDK 来尝试这些功能。以下是一些新引入的功能:

  • 主构造函数
  • 集合表达式
  • 默认 Lambda 参数
  • 任何类型的别名
  • 内联数组
  • 拦截器
  • 使用nameof访问实例成员

主构造函数

现在可以在任何 class 和 struct 中创建主构造函数。 主构造函数不再局限于 record 类型。 主构造函数参数都在类的整个主体的范围内。 为了确保显式分配所有主构造函数参数,所有显式声明的构造函数都必须使用 this() 语法调用主构造函数。 将主构造函数添加到 class 可防止编译器声明隐式无参数构造函数。 在 struct 中,隐式无参数构造函数初始化所有字段,包括 0 位模式的主构造函数参数。

编译器仅在 record 类型(record class 或 record struct 类型)中为主构造函数参数生成公共属性。 对于主构造函数参数,非记录类和结构可能并不总是需要此行为。

主构造函数的参数位于声明类型的整个主体中。 它们可以初始化属性或字段。 它们可用作方法或局部函数中的变量。 它们可以传递给基本构造函数。

主构造函数指示这些参数对于类型的任何实例是必需的。 任何显式编写的构造函数都必须使用 this(...) 初始化表达式语法来调用主构造函数。 这可确保主构造函数参数绝对由所有构造函数分配。 对于任何 class 类型(包括 record class 类型),当主构造函数存在时,不会发出隐式无参数构造函数。 对于任何 struct 类型(包括 record struct 类型),始终发出隐式无参数构造函数,并始终将所有字段(包括主构造函数参数)初始化为 0 位模式。 如果编写显式无参数构造函数,则必须调用主构造函数。 在这种情况下,可以为主构造函数参数指定不同的值。

下面看下主构造函数的应用场景

初始化属性

以下代码初始化从主构造函数参数计算的两个只读属性:

public readonly struct Distance(double dx, double dy)
{
public readonly double Magnitude = Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction = Math.Atan2(dy, dx);
}

前面的代码演示了用于初始化计算的只读属性的主构造函数。 Direction 和 Magnitude 的字段初始值设定项使用主构造函数参数。 主构造函数参数不会在结构中的其他任何位置使用。 前面的结构就像编写了以下代码一样:

public readonly struct Distance
{
public readonly double Magnitude { get; } public readonly double Direction { get; } public Distance(double dx, double dy)
{
Magnitude = Math.Sqrt(dx * dx + dy * dy);
Direction = Math.Atan2(dy, dx);
}
}

当需要参数来初始化字段或属性时,利用新功能可以更轻松地使用字段初始值设定项。

创建可变状态

前面的示例使用主构造函数参数来初始化只读属性。 如果属性不是只读的,你还可以使用主构造函数。 考虑下列代码:

public struct Distance(double dx, double dy)
{
public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction => Math.Atan2(dy, dx); public void Translate(double deltaX, double deltaY)
{
dx += deltaX;
dy += deltaY;
} public Distance() : this(0,0) { }
}

在前面的示例中,Translate 方法了更改 dx 和 dy 组件。 这就需要在访问时计算 Magnitude 和 Direction 属性。 => 运算符指定一个以表达式为主体的 get 访问器,而 = 运算符指定一个初始值设定项。 此版本将无参数构造函数添加到结构。 无参数构造函数必须调用主构造函数,以便初始化所有主构造函数参数。

依赖关系注入

主构造函数的另一个常见用途是指定依赖项注入的参数。 下面的代码创建了一个简单的控制器,使用时需要有一个服务接口:

public interface IService
{
Distance GetDistance();
} public class ExampleController(IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Distance> Get()
{
return service.GetDistance();
}
}

主构造函数清楚地指明了类中所需的参数。 使用主构造函数参数就像使用类中的任何其他变量一样。

初始化基类

可以从派生类的主构造函数调用基类的主构造函数。 这是编写必须调用基类中主构造函数的派生类的最简单方法。 例如,假设有一个类的层次结构,将不同的帐户类型表示为一个银行。 基类类似于以下代码:

public class BankAccount(string accountID, string owner)
{
public string AccountID { get; } = accountID;
public string Owner { get; } = owner; public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
}

一个派生类将呈现一个支票帐户:

public class CheckAccount(string accountID, string owner, decimal overdraftLimit = 0) : BankAccount(accountID, owner)
{
public decimal CurrentBalance { get; private set; } = 0; public void Deposit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit amount must be positive");
}
CurrentBalance += amount;
} public void Withdrawal(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Withdrawal amount must be positive");
}
if (CurrentBalance - amount < -overdraftLimit)
{
throw new InvalidOperationException("Insufficient funds for withdrawal");
}
CurrentBalance -= amount;
} public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}, Balance: {CurrentBalance}";
}

总结

通过合理有效地利用主构造函数,我们可以创造出更灵活、更强大、更可控的代码构造。

集合表达式

集合表达式引入了新的语法来创建常见的集合值。 可以使用展开运算符 .. 将其他集合内联到这些值中。

以下示例演示了集合表达式的使用:

// Create an array:
int[] a = [1, 2, 3, 4, 5, 6, 7, 8]; // Create a span
Span<int> b = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i']; // Create a 2 D array:
int[][] twoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; // create a 2 D array from variables:
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[][] twoDFromVariables = [row0, row1, row2];

总结

集合表达式使得代码更简洁,操作更便捷。

默认 Lambda 参数

现在可以为 Lambda 表达式的参数定义默认值。 语法和规则与将参数的默认值添加到任何方法或本地函数相同。

Func<int, string, bool> isTooLong = (int x, string s = "") => s.Length > x;

总结

默认 Lambda 参数,弥补了Lambda不能设置默认参数的缺陷。

任何类型的别名

可以使用 using 别名指令创建任何类型的别名,而不仅仅是命名类型。 这意味着可以为元组类型、数组类型、指针类型或其他不安全类型创建语义别名。

using Point = (int x, int y);

总结

它提供了一个简短的,由开发者提供的名称,可以用来替代那些完整的结构形式。

内联数组(Inline Arrays)

运行时团队和其他库作者使用内联数组来提高应用的性能。 内联数组使开发人员能够创建固定大小的 struct 类型数组。 具有内联缓冲区的结构应提供类似于不安全的固定大小缓冲区的性能特征。 你可能不会声明自己的内联数组,但当它们从运行时 API 作为 System.Span 或 System.ReadOnlySpan 对象公开时,你将透明地使用这些数组。

内联数组的声明类似于以下 struct:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private int _element0;
}

它们的用法与任何其他数组类似:

var buffer = new Buffer();
for (int i = 0; i < 10; i++)
{
buffer[i] = i;
} foreach (var i in buffer)
{
Console.WriteLine(i);
}

区别在于编译器可以利用有关内联数组的已知信息。 你可能会像使用任何其他数组一样使用内联数组。

总结

内联数组对性能提高帮助很大。

拦截器(Interceptors)

警告:本次发布的预览版引入了一项叫做interceptors(拦截器)的新功能。这项新功能主要用于一些高级场景,尤其是将会带来更好的AOT编译能力。作为.NET 8的实验性功能,在未来的版本中有可能被修改甚至删除,因此,它不应该在生产环境中使用。

拦截器是一种方法,该方法可以在编译时以声明方式将对可拦截方法的调用替换为对其自身的调用。 通过让拦截器声明所拦截调用的源位置,可以进行这种替换。 此过程可以向编译中(例如在源生成器中)添加新代码,从而提供更改现有代码语义的有限能力。

在源生成器中使用拦截器修改现有编译的代码,而非向其中添加代码。 源生成器将对可拦截方法的调用替换为对拦截器方法的调用。

总结

拦截器很强大,进一步了解可以参考下面连接:

https://github.com/dotnet/roslyn/blob/main/docs/features/interceptors.md

使用nameof访问实例成员

曾经为了访问实例成员,你频繁地编写nameof感到非常恼火吗?好消息是,C# 12 Preview 3为你带来解决方案。让我们一起看看这个神奇的功能是如何工作的:

记得以前,当尝试使用nameof关键字去访问一个实例字段时,你必须有一个对象的实例,对吧?

现在,告别这些限制吧!有了C# 12 Preview 3,我们只需要类就可以做到这一点。

给出一个实际的例子,让我们看看这个独特的特性在这段代码中是如何发挥作用的:

internal class NameOf
{
public string S { get; } = "";
public static int StaticField;
public string NameOfLength { get; } = nameof(S.Length);
public static void NameOfExamples()
{
Console.WriteLine(nameof(S.Length)); // 使用`nameof`访问实例成员
Console.WriteLine(nameof(StaticField.MinValue)); // 使用`nameof`访问静态字段
}
[Description($"String {nameof(S.Length)}")]
public int StringLength(string s)
{ return s.Length; }
}

你看到nameof如何处理S.Length 和 StaticField.MinValue了吗?这是C# 12 Preview 3的新特性!你不需要一个实例就可以获取S.Length的名称。你也可以用nameof获取StaticField.MinValue。

简单来说,想象你有一个叫做"NameOf"的玩具盒。以前,你必须爬进盒子里才能找到你最喜欢的玩具。

但现在呢?你只需要告诉你的魔术盒你想要什么(比如,你想要的玩具魔方的长度,或者芭蕾舞泰迪熊的最小数量),它就会给你,都不用进去!

总结

nameof的增强,让代码更少,逻辑更简单。

参考文档:

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12

原文地址:https://blog.baibaomen.com/c-12-%e4%b8%ad%e7%9a%84%e6%96%b0%e5%a2%9e%e5%8a%9f%e8%83%bd/

C# 12 中的新增功能的更多相关文章

  1. ADO.NET 中的新增功能

    ADO.NET 中的新增功能: .NET Framework (current version) 以下是 .NET Framework 4.5 中 ADO.NET 的新增功能. SqlClient D ...

  2. WPF4.5 中的新增功能和增强功能的信息

    本主题包含有关 Windows Presentation Foundation (WPF) 版本 4.5 中的新增功能和增强功能的信息. 本主题包含以下各节: 功能区控件 改善性能,当显示大时设置分组 ...

  3. .NET Framework 4.5、4.5.1 和 4.5.2 中的新增功能

    .NET Framework 4.5.4.5.1 和 4.5.2 中的新增功能 https://msdn.microsoft.com/zh-cn/library/ms171868.aspx

  4. Windows 10 开发人员预览版中的新增功能(转自 IT之家)

    Windows 10 开发人员预览版中的新增功能 在Win10预览版中安装工具与SDK后,即可着手创建Windows通用应用或先浏览目前的环境与此前相比都发生了什么变化. 应用建模 文件资源管理器: ...

  5. C# 中的新增功能

    百度搜索:C# 中的新增功能 微软有站点专门介绍:C# 中的新增功能. 地址:https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/inde ...

  6. .NET平台系列14 .NET5中的新增功能

    系列目录     [已更新最新开发文章,点击查看详细] .NET5中不包含的内容 尽管 .NET5 框架中提供了一组重要 API,但它并不包括过去20年左右开发的所有 API,但是.NET Stand ...

  7. PHP V5.2 中的新增功能,第 1 部分: 使用新的内存管理器

    PHP V5.2:开始 2006 年 11 月发布了 PHP V5.2,它包括许多新增功能和错误修正.它废止了 5.1 版并被推荐给所有 PHP V5 用户进行升级.我最喜欢的实验室环境 —— Win ...

  8. C# 6.0 中的新增功能(.NET Framework 4.6 与 Visual Studio 2015 )

    C#6.0 在 2015 年7月随着.NET Framework 4.6 一同发布,后期发布了.NET Framework 4.6.1,4.6.2. 一.自动属性初始化(Auto-property i ...

  9. .NET Framework3.0/3.5/4.0/4.5新增功能摘要

    Microsoft .NET Framework 3.0 .NET Framework 3.0 中增加了不少新功能,例如: Windows Workflow Foundation (WF) Windo ...

  10. Windows Server 2016-存储新增功能

    本章给大家介绍有关Windows Server 2016 中存储方面的新增功能,具体内容如下: 1.Storage Spaces Direct: 存储空间直通允许通过使用具有本地存储的服务器构建高可用 ...

随机推荐

  1. 整理spring-web里支持的文件以及对应的Content-Type

    前言 最近在弄文件上传.下载.在线预览时经常需要设置请求标头或者响应标头的Content-Type 属性.所以研究了一下spring支持哪些Content-Type,通过研究MediaTypeFact ...

  2. 大语言模型的开发利器langchain

    目录 简介 什么是langchain langchain的安装 langchain快速使用 构建应用 聊天模式 Prompt的模板 Chains Agents Memory 总结 简介 最近随着cha ...

  3. Java 基础复习——StringBuffer 和 StringBuilder

    StringBuffer 和 StringBuilder StringBuffer 类 简介 java.lang.StringBuffer 代表可变的字符序列,可以对字符串内容进行增删 很多方法和 S ...

  4. Pyinstaller打包 Pytest+Allure成exe文件执行时,报错ERROR: usage: apitest.exe [options] [file_or_dir] [file_or_dir] [...] xxx.exe: error: unrecognized arguments: --alluredir=.\\report\\xml --clean-alluredir

    网上找了很多案例啊  都没解决问题,由本人的多次试验 终于成功解决 1.打包运行 pyinstaller -D  xxx.py 打包成功后  执行exe报错  如下 2.此情况是说明  命令无法正确识 ...

  5. 【Shell】ps 命令

    ps 命令 Linux ps (英文全拼:process status)命令用于显示当前进程的状态,类似于 windows 的任务管理器. 1. ps 常用的命令 ps -aux #显示所有进程信息 ...

  6. tensorflow神经网络归一化方法

    参考https://blog.csdn.net/chary8088/article/details/81542879

  7. Windows10 下 Neo4j1.5.8 安装教程

    前言 Neo4j 是一个高性能的.NOSQL 图形数据库,它将结构化数据存储在网络上而不是表中.基于磁盘的.具备完全的事务特性的 Java 持久化引擎,这里就不把他和常用关系型数据库做对比了.因为篇幅 ...

  8. Win32API中的宽字符

    4.1了解什么是Win32API Win32API就是windows操作系统提供给我们的函数(应用程序接口),其主要存放在C:\Windows\System32 (存储的DLL是64位).C:\Win ...

  9. 长连接:chatgpt流式响应背后的逻辑

    一.前言: 提起长连接,我们并不陌生,最常见的长连接非websocket莫属了.即使没有在项目中实际用过,至少也应该有所接触.长连接指在一次网络通信中,客户端与服务器之间建立一条持久的连接,可以在多次 ...

  10. Cilium系列-13-启用XDP加速及Cilium性能调优总结

    系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, ...