新的 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. Neo4J 图库的集群部署与基础使用

    Ned4J 图库的集群部署与基础使用 部署机器 名称 配置 IP server1 8 核 16G 172.16.0.2 server2 8 核 16G 172.16.0.3 server3 8 核 1 ...

  2. 基于生成式预训练Transformer的跨语言文本摘要与情感分析

    目录 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成 ...

  3. 逍遥自在学C语言 | 指针陷阱-空指针与野指针

    前言 在C语言中,指针是一种非常强大和灵活的工具,但同时也容易引发一些问题,其中包括空指针和野指针. 本文将带你了解这两个概念的含义.产生原因以及如何避免它们所导致的问题. 一.人物简介 第一位闪亮登 ...

  4. .NET写一个自己的Lambda表达式与表达式树

    LambdaExpression继承Expression Expression又继承LambdaExpressio 所以,Expression与 Expression的区别在于:泛型类以静态类型的方法 ...

  5. iOS CoreData总结

    相关主要类: NSManagedObjectContext 管理对象,上下文,持久性存储模型对象,处理数据与应用的交互 NSManagedObjectModel 被管理的数据模型,数据结构 NSPer ...

  6. vscode中react组件

    通过使用这个插件我们可以很方便的进行组件/方法/文件的导入 本篇博客仅对插件进行介绍翻译,便于自己以后使用 常用片段列表 imr: 引入 React import React from 'react' ...

  7. 【技术积累】Mysql中的SQL语言【技术篇】【三】

    聚合函数 SUM函数 在MySQL中,SUM函数是用于计算数值列的总和的聚合函数.它接受一个数值列作为参数,并返回该列中所有值的总和. 以下是一个使用SUM函数的示例: 假设我们有一个名为" ...

  8. 反汇编分析C语言

    环境 VC6.0环境 空函数反汇编 #include "stdafx.h" void function(){ } int main(int argc, char* argv[]) ...

  9. C++子类的构造函数

    子类的构造函数 子类可以有自己的构造函数 子类没有构造函数,默认系统会调用父类的构造函数 子类有自己的构造函数,系统会先运行父类的构造函数,随后运行子类的构造函数,对子类对象进行覆盖和拓展 即不论子类 ...

  10. ASP.NET WebForm中在TextBox输入框回车时会触发其他事件,如何处理?

    一.TextBox在输入框回车时会触发其他事件,如何解决? 前台代码: <ul> <li><span>名称:</span><asp:TextBox ...