首页 > 编程语言 >C# 12 中的新增功能

C# 12 中的新增功能

时间:2024-02-20 11:15:05浏览次数:25  
标签:12 nameof C# double 新增 函数参数 dx public 构造函数

C# 12 中的新增功能

 

运行效果

新的 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/

标签:12,nameof,C#,double,新增,函数参数,dx,public,构造函数
From: https://www.cnblogs.com/sexintercourse/p/18022638

相关文章

  • uniapp编译成微信小程序报错-Component is not found in path "components/canvaspage
     问题:我需要将components/canvaspagebg/index引入进pages/index/index   报错了pages/index/index页面引入: uni-app程序编译成微信小程序后,组件无法显示,控制台报错,错误信息为: 我查看了路径,是对的看网上的解决办法就是 我取消勾选后刷新页面就可以了,此时我在选中......
  • linux centos重启后lvm异常。 LV Status NOT available
    问题描述1.机器无法正常开机。通过启动日志查看是挂载/data目录失败。 解决进入紧急模式取消/etc/fstab中的开机启动挂载2.进入系统后查看硬盘手动挂载测试。发现vgslvspvs都是有,但/dev/mapper/中没找到 通过lvdisplay查看lv的状态,发现status异常 StatusNOTa......
  • java1.8LocalDate日期常用函数
    LocalDatenow=LocalDate.now();//获取当前日期2023-12-31Stringformat=LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss"));//获取当前时间2023-12-3115:44:52intmonthValue=now.getMonthValue();//返回当前的月份intdayOfMonth=now.......
  • 洛谷题单指南-递推与递归-P3612 [USACO17JAN] Secret Cow Code S
    原题链接:https://www.luogu.com.cn/problem/P3612题意解读:字符串加长的时候,是先把最后一个字符接上,再拼接其余字符,注意不是翻转,要找第n个字符,就要看字符串加长几次后长度能超过n,然后在加长后的字符串中找第n个字符。解题思路:如果直接通过模拟法,字符串长度太长,且要找的第n个数......
  • Splunk转发器centos部署
    Spunk概述1)Splunk分为服务器(Splunk)和客户端(Splunkforwarder)。Splunk的服务器就是索引器和接收器。客户端就是数据的转发器。顾名思义就是数据可由客户端转发至server端进行索引。客户端只起到转发数据的作用。2)Splunk是一款功能强大,功能强大且完全集成的软件,用于实时企业日......
  • el-cascader 动态加载,编辑时数据回显
    参考链接:https://blog.csdn.net/qq_16382227/article/details/129735904业务:只有选择了关联摄像头以后才能显示出选择预置位,关联摄像头是联级逐次触发掉接口加载的,后续还需要回显。html:     <el-cascader      v-model="ruleForm.cameraTree"  ......
  • Docker-Compose简单入门使用
    Dockercompose使用DockerCompose官方文档:https://docs.docker.com/compose/一、DockerCompose安装如果安装使用DockerDesktop默认就安装了DockerCompose,dockerCompose安装参考:https://www.cnblogs.com/morang/p/devops-docker24-composev2-install.htmlhttps://blog......
  • RocketMQ源码系列(1) — 基础架构
    基础架构我们先通过下面这张图来整体的对RocketMQ有一个基础架构上的认识。RocketMQ主要有四个角色:NameServer、Broker、Producer、Consumer。Broker每台机器上部署的RocketMQ进程一般称之为Broker,生产者向Broker发送消息,Broker收到生产者的消息后存储到本地磁盘文件中......
  • Codeforces Round 928 (Div. 4)
    总结一下最近:感觉过于追求进度了,没有好好的把每题都吃透消化,然后有点依赖题解了,没有好好的思考...B.VladandShapesB题输入二维数组的时候不可以直接两个for循环然后cin,要读入char,再转为数字赋值给二维数组,因为他读入的时候不带有空格而int是要有空格的,这样子比如读000就把它......
  • Docker 使用遇到的问题解决 更改Tag
    dockertagconsul:1.15.4consul:latestdockerrmiconsul:1.15.4删除制定版本在运行时,有些镜像拉取时报错我这里 时 consu,只能制定版本下载1.15.4Errorresponsefromdaemon:manifestforconsul:latestnotfound:manifestunknown:manifestunknown ......