技术速递|使用 C# 集合表达式重构代码

作者:David Pine
排版:Alan Wang

本文是系列文章的第二篇,该系列文章涵盖了探索 C# 12功能的各种重构场景。在这篇文章中,我们将了解如何使用集合表达式重构代码,我们将学习集合初始化器、各种表达式用法、支持的集合目标类型和 spread 语法。该系列的进展情况如下:

  1. 使用主构造函数重构 C# 代码
  2. 使用集合表达式重构 C# 代码(本文)
  3. 通过为任何类型添加别名来重构您的 C# 代码
  4. 重构您的 C# 代码以使用默认 lambda 参数

这些功能延续了我们的旅程,使我们的代码更具可读性和可维护性,并且被认为是开发人员应该了解的“日常 C#”功能。

集合表达式

C# 12 引入了集合表达式,它为许多不同的集合类型提供简单且一致的语法。当使用集合表达式初始化集合时,编译器生成的代码在功能上与使用集合初始化项等效。该功能强调一致性,同时允许编译器优化低级的 C#。当然,每个团队都可以决定采用哪些新功能,如果您愿意,您可以尝试并引入这种新语法,因为之前所有初始化集合的方法都将继续工作。

对于集合表达式,元素出现在左括号 [ 和右括号 ] 之间的内联元素序列。继续阅读以了解有关集合表达式如何工作的更多信息。

初始化

C# 提供了许多语法来初始化不同的集合。集合表达式取代了所有这些,所以让我们先来看看初始化整数数组的不同方法,如下所示:

var numbers1 = new int[3] { 1, 2, 3 };
var numbers2 = new int[] { 1, 2, 3 };
var numbers3 = new[] { 1, 2, 3 };
int[] numbers4 = { 1, 2, 3 };

这四个版本在功能上都是等效的,并且编译器为每个版本生成相同的代码。最后一个示例类似于新的集合表达式语法。如果您眯起眼睛,将花括号 { 和 } 想象为方括号 [ 和 ],然后您就会读到新的集合表达式语法了。集合表达式不使用花括号,这是为了避免与现有语法产生歧义,特别是用 { } 来表示模式中的任何非空。

最后一个示例是唯一显式声明类型,而不是依赖 var。以下示例创建一个 List:

List<char> david = [ 'D', 'a', 'v', 'i', 'd' ];

同样,集合表达式不能与 var 关键字一起使用。您必须声明类型,因为集合表达式目前没有自然类型,以及可以转换为多种集合类型。对 var 赋值的支持仍在考虑中,但团队尚未确定自然类型应该是什么。换句话说,在编写以下代码时,C# 编译器会出错并显示 CS9176:集合表达式没有目标类型:

// Error CS9176: There is no target type for the collection expression
var collection = [1, 2, 3];

您可能会问自己,“既然有这么多不同的方法来初始化集合,为什么我要使用新的集合表达式语法?” 答案是,通过集合表达式,您可以使用相同的语法以一致的方式表达集合。这有助于提高代码的可读性和可维护性。我们将在接下来的部分中探讨更多优势。

集合表达式变化

您可以使用以下语法表示集合为空:

int[] emptyCollection = [];

​空集合表达式的初始化是代替以前使用“new”关键字的代码的绝佳选择,因为它已被编译器优化,以避免为某些集合类型分配内存。例如,当集合类型是数组 T[] 时,编译器会生成 Array.Empty(),它比 new int[] { } 效率更高。另一种快捷方式是使用集合表达式中的元素数量来设置集合大小,例如对于 Listx = [1, 2];使用 new List(2)。

集合表达式还允许您在不声明显式类型的情况下赋值给接口。编译器确定用于 IEnumerable、IReadOnlyList和 IReadOnlyCollection等类型的类型。如果实际使用的类型很重要,您需要声明它,因为如果有更高效的类型可用,情况可能会发生变化。同样,在编译器无法生成更高效的代码的情况下,例如当集合类型是 List时,编译器会生成一个新的 List(),它是等效的。

使用空集合表达式的优点有三个:

  • 它提供了初始化所有集合的一致方法,无论其目标类型如何。
  • 它允许编译器生成高效的代码。
  • 需要编写的代码更少。例如,您可以简单地编写 [],而不是编写 Array.Empty()或 Enumerable.Empty()。

关于高效生成代码的更多细节:使用 [] 语法生成已知的 IL。这允许运行时通过重用 Array.Empty(对于每个 T)的存储来优化,甚至更积极地内联代码。

空集合可以满足它们的目的,但是您可能需要一个具有一些初始值的集合。您可以使用以下语法用单个元素初始化集合:

string[] singleElementCollection =
["one value in a collection"
];

初始化单个元素集合类似于初始化包含多个单个元素的集合。您可以使用以下语法通过添加其他文字值来初始化包含多个元素的集合:

int[] multipleElementCollection = [1, 2, 3 /* any number of elements */];

一些历史
该功能的早期提案包括短语“集合文字”,您可能听说过与此功能相关的术语。这似乎是显而易见且合乎逻辑的,特别是考虑到前面的几个例子。 所有元素均表示为文字值。 但您不局限于使用文字。事实上,只要类型一致,您就可以轻松地使用变量初始化集合(当它们不对应时,可以使用隐式转换)。

让我们看另一个代码示例,但它使用 spread 元素来包含另一个集合的元素,使用以下语法:

int[] oneTwoThree = [1, 2, 3];
int[] fourFiveSix = [4, 5, 6];
int[] all = [.. fourFiveSix, 100, .. oneTwoThree];
Console.WriteLine(string.Join(", ", all));
Console.WriteLine($"Length: {all.Length}");
// Outputs:
//   4, 5, 6, 100, 1, 2, 3
//   Length: 7

Spread 元素是一个强大的功能,它允许您将另一个集合的元素包含在当前集合中。spread 元素是一种以简洁的方式组合集合的好方法。Spread 元素中的表达式必须是可枚举的(可查询的)。有关更多信息,请参阅 Spread 部分。

支持的集合类型

集合表达式可以与许多目标类型一起使用。该功能可识别代表集合类型的“形状”。因此,您熟悉的大多数集合都是开箱即用的。对于与该“形状”不匹配的类型(主要是只读集合),您可以应用一些属性来描述构建器模式。BCL 中需要属性/构建器模式方法的集合类型已经更新。

  • 您不太可能需要考虑如何选择目标类型,但如果您对规则感到好奇,请参阅 C# 语言参考:集合表达式 - 转换。

  • 集合表达式尚不支持字典。您可以找到扩展功能的提案:C# 功能提案:字典表达式。

重构场景

集合表达式在许多场景中都很有用,例如:

  • 初始化声明非空集合类型的空集合:
    • 字段
    • 属性
    • 局部变量
    • 方法参数
    • 返回值
    • 合并表达式作为最终的解决方案,以安全地避免异常
  • 将参数传递给需要集合类型参数的方法

让我们利用本节来探索一些示例使用场景,并考虑潜在的重构机会。当您定义包含非空集合类型的字段和/或属性的类或结构时,可以使用集合表达式来初始化它们。例如,请考虑以下 ResultRegistry 对象示例:

namespace Collection.Expressions;
public sealed class ResultRegistry
{private readonly HashSet<Result> _results = new HashSet<Result>();public Guid RegisterResult(Result result){_ = _results.Add(result);return result.Id;}public void RemoveFromRegistry(Guid id){_ = _results.RemoveWhere(x => x.Id == id);}
}
public record class Result(bool IsSuccess,string? ErrorMessage
)
{public Guid Id { get; } = Guid.NewGuid();
}

在前面的代码中,结果注册表类包含一个私有 _results 字段,该字段使用新的 HashSet()构造函数表达式进行初始化。在您选择的 IDE(支持这些重构功能)中,右键单击 new 关键字,选择 Quick Actions and Refactorings…(或按Ctrl + .),然后选择“Collection initialization can be simplified”,如下视频所示:

refactor-simplify-collection

代码已更新为使用集合表达式语法,如以下代码所示:

private readonly HashSet<Result> _results = [];

前面的代码使用 new HashSet()构造函数表达式实例化了 HashSet。 然而,在这种情况下 [] 是等效的。

Spread

许多流行的编程语言(例如 Python 和 JavaScript/TypeScript 等)都提供了 spread 语法的变体,这是一种简洁的处理集合的方式。在 C# 中,spread 元素是用于将各种集合串联成单个集合的语法。

正确的术语
Spread 元素经常与术语“spread运算符”混淆。在 C# 中,不存在“spread运算符”这样的东西。… 表达式不是运算符,它是 spread 元素语法一部分的表达式。根据定义,此语法与运算符的语法不一致,因为它不对操作数执行操作。例如,… 表达式已经存在于范围切片模式中,并且也可以在列表模式中找到。

那么 spread 元素到底是什么?它从正在“spread”的集合中获取各个值,并将它们放置在目标集合中的相应位置。Spread 元素功能还带来了重构机会。如果您有调用 .ToList 或 .ToArray 的代码,或者您想要使用即时求值,您的 IDE 可能会建议改用 spread 元素语法。例如,以下代码:

namespace Collection.Expressions;
public static class StringExtensions
{public static List<Query> QueryStringToList(this string queryString){List<Query> queryList = (from queryPart in queryString.Split('&')let keyValue = queryPart.Split('=')where keyValue.Length is 2select new Query(keyValue[0], keyValue[1])).ToList()
;return queryList;}
}
public record class Query(string Name, string Value);

可以重构前面的代码以使用 spread 元素语法,请考虑以下代码,该代码删除了 .ToList 方法调用,并使用表达式主体方法作为额外的重构版本:

public static class StringExtensions
{public static List<Query> QueryStringToList(this string queryString) =>[.. from queryPart in queryString.Split('&')let keyValue = queryPart.Split('=')where keyValue.Length is 2select new Query(keyValue[0
], keyValue[
1
])];
}

Span 和 ReadOnlySpan 支持

集合表达式支持 Span和 ReadOnlySpan类型,用于表示任意内存的连续区域。即使您不在代码中直接使用它们,您也可以从它们提供的性能改进中受益。集合表达式允许运行时提供优化,特别是当集合表达式用作参数时可以选择使用 span 的重载。

如果您的应用程序使用 span,您也可以直接赋值给 span:

Span<int> numbers = [1, 2, 3, 4, 5];
ReadOnlySpan<char> name = ['D', 'a', 'v', 'i', 'd'];

如果您使用 stackalloc 关键字,甚至还提供了使用集合表达式的重构。例如,以下代码:

namespace Collection.Expressions;
internal class Spans
{public void Example(){ReadOnlySpan<byte> span = stackalloc byte[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};UseBuffer(span);}private static void UseBuffer(ReadOnlySpan<byte> span){// TODO://   Use the span...throw new NotImplementedException();}
}

如果右键 stackalloc 关键字,选择 Quick Actions and Refactorings…(或者按 Ctrl + .),选择 Collection initialization can be simplified,如下视频所示:

refactor-collection-ex

代码已更新为使用集合表达式语法,如以下代码所示:

namespace Collection.Expressions;
internal class Spans
{public void Example(){ReadOnlySpan<byte> span =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10
];UseBuffer(span);}// Omitted for brevity...
}

有关详细信息,请参阅 Memory 和 Span 使用指南。

语义考虑

当使用集合表达式初始化集合时,编译器生成的代码在功能上与使用集合初始化项等效。有时,生成的代码比使用集合初始化项更有效。如以下示例:

List<int> someList = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

集合初始化项的规则要求编译器为初始化项中的每个元素调用 Add 方法。但是,如果您要使用集合表达式语法:

List<int> someList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

编译器生成的代码改为使用 AddRange,这可能更快或更优化。 编译器能够进行这些优化,因为它知道集合表达式的目标类型。

后续步骤

请务必在您自己的代码中尝试一下!敬请期待本系列的下一篇文章,我们将探讨如何通过为任何类型添加别名来重构 C# 代码。同时,您可以在以下资源中了解有关集合表达式的更多信息:

  • C# 功能提案:集合表达式

  • C# 语言参考:集合表达式

  • C# 文档:集合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/17137.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

函数调用时长的关键点:揭秘参数位置的秘密

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、默认参数的秘密 示例代码 二、关键字参数与位置参数的舞蹈 示例代码 总结 一、默认参…

Linux下SuiteSparse的下载与编译

前言 SuiteSparse 是一个用于稀疏矩阵计算的开源库&#xff0c;它提供了一系列高效的算法和工具&#xff0c;用于解决线性代数和优化问题中的稀疏矩阵操作。 SuiteSparse Matrix Collection 是由 Tim Davis 创建和维护的一个稀疏矩阵集合&#xff0c;其中包含了各种各样的真实…

Java学习:电影查询简单系统

1.创建一个movice的对象来存放电影 里面设置构造器&#xff08;有参和无参&#xff09; package com.movie;public class movice {//创建一个movice的对象存放电影private int id;private String name;private double price;private double score;private String diector;pri…

PyCharm面板ctrl+鼠标滚轮放大缩小代码

1.【File】➡【Settings】 2.点击【Keymap】&#xff0c;在右边搜索框中搜incre&#xff0c;双击出现的【Increase Font Size】 3.在弹出的提示框中选择【Add Mouse Shortcut】 4.弹出下面的提示框后&#xff0c;键盘按住【ctrl】&#xff0c;并且上滑鼠标滚轮。然后点击【O…

高等数学导学

高数内容线 1.极限2.导数3.积分一元函数多元函数&#xff1a; 说明 高等数学主要讲这三个东西&#xff0c;上下两册内容&#xff1a;上册主要讲一元&#xff0c;下册讲多元 但是一元是多元的基础&#xff0c;必须得掌握好&#xff0c;下册的多元函数才能学的好 七八章讲微分和解…

ResizeObserver loop completed with undelivered notifications.

报错信息 ResizeObserver loop completed with undelivered notifications. 来源 在用vue3 element-plus写项目的时候报的错&#xff0c;经过排查法&#xff0c;发现是element-plus的el-table组件引起的错误。 经过初步排查&#xff0c;这个错误并不是vue以及element-plus…

【前端每日基础】day24——DOM操作

DOM 操作 获取元素 要对网页中的元素进行操作&#xff0c;首先需要获取这些元素。常用的方法有&#xff1a; document.getElementById(id): 获取具有指定id的元素。 document.getElementsByClassName(className): 获取具有指定类名的所有元素&#xff0c;返回HTMLCollection。…

Redis数据类型(上篇)

前提&#xff1a;&#xff08;key代表键&#xff09; Redis常用的命令 命令作用keys *查看当前库所有的keyexists key判断某个key是否存在type key查看key是什么类型del key 删除指定的keyunlink key非阻塞删除&#xff0c;仅仅将keys从keyspace元数据中删除&#xff0c;真正的…

vueRouter路由总结

https://blog.csdn.net/qq_24767091/article/details/119326884

中国电子节能技术协会数据安全专业委员会筹备会暨标准征集启动会即将开幕

导读&#xff1a;“以高效、安全、绿色数据底座铸就美好未来”为主题的2024数据安全与绿色发展研讨会&#xff0c;中国电子节能技术协会数据安全专业委员会筹备会暨标准征集启动会即将开幕。 绿色发展是高质量发展的底座&#xff0c;绿色化是新一轮科技革命和产业变革的重要趋势…

力扣hot 100:49. 字母异位词分组(python C++)

目录 题目描述&#xff1a;题解&#xff08;python&#xff09;&#xff1a;&#xff08;方法一&#xff1a;排序&#xff09;代码解析代码运行解析 题解&#xff08;C&#xff09;&#xff1a;&#xff08;方法一&#xff1a;排序&#xff09;代码解析&运行解析 原题目链接…

机器学习笔记——K近邻算法、手写数字识别

KNN算法 “物以类聚&#xff0c;人以群分”相似的数据往往拥有相同的类别 其大概原理就是一个样本归到哪一类&#xff0c;当前样本需要归到频次最高的哪个类去 也就是说有一个待分类的样本&#xff0c;然后跟他周围的k个样本来看&#xff0c;k中哪一个类最多&#xff0c;待分类…

Oracle数据库Day01-SELECT语句

一、SQL语句 1. 环境配置与准备 linux端oracle用户打开监听//查看监听状态与开始监听 lsnrctl status lsnrctl start开启数据库sqlplus / as sysdba startup;解锁hr用户样例数据库&#xff0c;给hr用户设置密码并且连接alter user hr account unlock; alter user hr identifie…

2024爆款神器!会声会影2024旗舰版,让你的视频制作技能暴涨,不学真的亏大了!

在数字内容创作的时代&#xff0c;视频编辑已经成为连接创意与现实的重要桥梁。无论是个人Vlog制作、在线教育课程、企业宣传还是专业影视制作&#xff0c;高效而强大的视频编辑软件成为了必不可少的工具。会声会影2024旗舰版&#xff0c;作为一款集先进技术与用户友好界面设计…

常用API(正则表达式、爬取、捕获分组和非捕获分组 )

1、正则表达式 练习——先爽一下正则表达式 正则表达式可以校验字符串是否满足一定的规则&#xff0c;并用来校验数据格式的合法性。 需求&#xff1a;假如现在要求校验一个qq号码是否正确。 规则&#xff1a;6位及20位之内&#xff0c;0不能在开头&#xff0c;必须全部是数字…

30.哀家要长脑子了!---栈与队列

1.388. 文件的最长绝对路径 - 力扣&#xff08;LeetCode&#xff09; 其实看懂了就还好 用一个栈来保存所遍历过最大的文件的绝对路径的长度&#xff0c;栈顶元素是文件的长度&#xff0c;栈中元素的个数是该文件目录的深度&#xff0c;非栈顶元素就是当时目录的长度 检查此…

Qt 5前后调色板差异变化

Qt 5之前&#xff1a; QPalette palette;//调色板 设置背景颜色 palette.setColor(QPalette::Backgound, color...);Qt 5之后&#xff1a; 由原有的 Background 模式 更新为 Window 模式 QPalette palette;//调色板 设置背景颜色 palette.setColor(QPalette::Window, color..…

10.SpringBoot 统一处理功能

文章目录 1.拦截器1.1在代码中的应用1.1.1定义拦截器1.1.2注册配置拦截器 1.2拦截器的作用1.3拦截器的实现 2.统一数据返回格式2.1 为什么需要统⼀数据返回格式&#xff1f;2.2 统⼀数据返回格式的实现 3.统一异常处理4.SpringBoot专业版创建项目无Java8版本怎么办&#xff1f;…

nodejs安装配置

nodejs安装 打开nodejs官网(https://nodejs.org/en/download/package-manager)&#xff0c;参考安装步骤操作。 更新镜像源 输入以下命令&#xff0c;将npm的镜像源设置为淘宝镜像。网上资料中&#xff0c;淘宝镜像地址多为https://registry.npm.taobao.org&#xff0c;这个…

【MATLAB源码-第67期】基于麻雀搜索算法(SSA)的无人机三维地图路径规划,输出最短路径和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 麻雀搜索算法&#xff08;Sparrow Search Algorithm, SSA&#xff09;是一种新颖的元启发式优化算法&#xff0c;它受到麻雀社会行为的启发。这种算法通过模拟麻雀的食物搜索行为和逃避天敌的策略来解决优化问题。SSA通过模拟…