当前位置: 首页 > news >正文

技术速递|使用 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# 文档:集合

相关文章:

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

作者&#xff1a;David Pine 排版&#xff1a;Alan Wang 本文是系列文章的第二篇&#xff0c;该系列文章涵盖了探索 C# 12功能的各种重构场景。在这篇文章中&#xff0c;我们将了解如何使用集合表达式重构代码&#xff0c;我们将学习集合初始化器、各种表达式用法、支持的集合目…...

我的世界开服保姆级教程

前言 Minecraft开服教程 如果你要和朋友联机时&#xff0c;可以选择的方法有这样几种&#xff1a; 局域网联机&#xff1a;优点&#xff1a;简单方便&#xff0c;在MC客户端里自带。缺点&#xff1a;必须在同一局域网内。 有些工具会带有联机功能&#xff1a;优点&#xff1a;一…...

[转载]同一台电脑同时使用GitHub和GitLab

原文地址&#xff1a;https://developer.aliyun.com/article/893801 简介&#xff1a; 工作中我们有时可能会在同一台电脑上使用多个git账号&#xff0c;例如&#xff1a;公司的gitLab账号&#xff0c;个人的gitHub账号。怎样才能在使用gitlab与github时&#xff0c;切换成对应…...

【网络协议】【OSI】一次HTTP请求OSI工作过程详细解析

目录 1. 一次HTTP请求OSI工作过程 1.1 应用层(第7层) 1.2 表示层(第6层) 1.3 会话层(第5层) 1.4 传输层(第4层)...

springboot vue 开源 会员收银系统 (2) 搭建基础框架

前言 完整版演示 前面我们对会员系统https://blog.csdn.net/qq_35238367/article/details/126174288进行了分析 确定了技术选型 和基本的模块 下面我们将从 springboot脚手架开发一套收银系统 使用脚手架的好处 不用编写基础的rabc权限系统将工作量回归业务本身生成代码 便于…...

Java进阶学习笔记26——包装类

包装类&#xff1a; 包装类就是把基本类型的数据包装成对象。 看下API文档&#xff1a; deprecated&#xff1a;极力反对、不赞成的意思。 marked for removal&#xff1a;标识为去除的意思。 自动装箱&#xff1a;基本数据类型可以自动转换成包装类。 自动拆箱&#xff1a;…...

【JavaEE进阶】——要想代码不写死,必须得有spring配置(properties和yml配置文件)

目录 本章目标&#xff1a; &#x1f6a9;配置文件 &#x1f6a9;SpringBoot配置文件 &#x1f388;配置⽂件的格式 &#x1f388; properties 配置⽂件说明 &#x1f4dd;properties语法格式 &#x1f4dd;读取配置文件 &#x1f4dd;properties 缺点分析 &#x1f3…...

第十四 Elasticsearch介绍和安装

docker-compose安装 kibana: image: docker.elastic.co/kibana/kibana:7.5.1 container_name: kibana ports: - "5601:5601" environment: ELASTICSEARCH_HOSTS: http://elasticsearch:9200 depends_on: - elasticsearch…...

YOLOv10介绍与推理--图片和视频演示(附源码)

导 读 本文主要对YOLOv10做简单介绍并给出推理图片和视频的步骤演示。 YOLOv10简介 YOLOv10是清华大学的研究人员在Ultralytics Python包的基础上&#xff0c;引入了一种新的实时目标检测方法&#xff0c;解决了YOLO 以前版本在后处理和模型架构方面的不足。通过消除非最大抑…...

Java实验08

实验一 demo.java package q8.demo02;public class demo{public static void main(String[] args) {WindowMenu win new WindowMenu("Hello World",20,30,600,290);} }WindowMenu.java package q8.demo02; import javax.swing.*;public class WindowMenu extends…...

MyBatis复习笔记

3.Mybatis复习 3.1 xml配置 properties&#xff1a;加载配置文件 settings&#xff1a;设置驼峰映射 <settings><setting name"mapUnderscoreToCamelCase" value"true"/> </settings>typeAliases&#xff1a;类型别名设置 #这样在映射…...

HTML的基石:区块标签与小语义标签的深度解析

&#x1f4da; HTML的基石&#xff1a;区块标签与小语义标签的深度解析 &#x1f310; 区块标签&#xff1a;构建网页的框架&#x1f3e0; <div>&#xff1a;万能的容器&#x1f4da; <section>、<article>、<aside>&#xff1a;语义化的布局 &#x1…...

Windows域控简介

一、Windows 域控概念 Windows 域控即 Active Directory&#xff08;AD&#xff09;域控制器&#xff0c;它是 Windows Server 中的一个角色&#xff0c;用于管理网络中的用户帐户、计算机和其他设备。AD 域控制器的功能包括&#xff1a; 用户认证&#xff1a;允许用户通过用…...

项目延期,不要随意加派人手

遇到软件项目出现延期的情况时&#xff0c;不建议随意加派人手。原因如下&#xff1a; 有些任务是不可拆分的&#xff0c;不能拆分为多个并行任务&#xff0c;增加人员不会加快项目进度。新增加人员需要原有人员介绍项目中的技术架构、业务知识&#xff0c;在开发过程中也难免…...

帝国CMS验证码不显示怎么回事呢?

帝国CMS验证码有时候会不显示或打叉&#xff0c;总结自己的解决方法。 1、检查服务器是否开启GD库 测试GD库是否开启的方法&#xff1a;浏览器访问&#xff1a;/e/showkey/index.php&#xff0c;如果出现一堆乱码或报错&#xff0c;证明GD库没有开启&#xff0c;开启即可。 2…...

【必会面试题】Redis 中的 zset数据结构

目录 Redis 中的 zset&#xff08;sorted set&#xff0c;有序集合&#xff09;数据结构在底层可以使用两种不同的实现&#xff1a;压缩列表&#xff08;ziplist&#xff09; 和 跳跃表&#xff08;skiplist&#xff09;。具体使用哪种结构取决于存储元素的数量和大小&#xff…...

括号匹配数据结构

括号匹配是一种数据结构问题&#xff0c;用于检查给定的字符串中的括号是否匹配。例如&#xff0c;对于字符串 "((())())"&#xff0c;括号是匹配的&#xff0c;而对于字符串 "())("&#xff0c;括号是不匹配的。 常见的解决括号匹配问题的数据结构是栈。…...

c语言:strcmp

strcmp函数是用于比较两个字符串的库函数&#xff0c;其功能是根据ASCII值逐一对两个字符串进行比较。 语法&#xff1a;strcmp(str1, str2) 返回值&#xff1a; 如果str1等于str2&#xff0c;则返回0。 如果str1小于str2&#xff0c;则返回负数&#xff08;具体值取决于C…...

传统关系型数据库与hive的区别

数据库和Hive之间存在本质的区别&#xff0c;主要体现在设计目的、数据处理方式、数据存储、查询延迟、数据更新能力、以及适用场景等方面。下面详细阐述它们之间的主要差异&#xff1a; 设计目的与应用场景&#xff1a; 数据库&#xff1a;主要是面向事务处理&#xff08;OLTP…...

windows-386、windows-amd64、windows-arm64这三者有什么区别?

选择文件的版本出现下面问题&#xff1a; Architectures windows-386 &#xff1a;这些是针对 32 位 Windows 系统编译的。windows-amd64 &#xff1a;这些是针对具有 AMD 或 Intel x86-64 架构的 64 位 Windows 系统编译的。windows-arm64 &#xff1a;这些是针对具有 ARM 架…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

MyBatis中关于缓存的理解

MyBatis缓存 MyBatis系统当中默认定义两级缓存&#xff1a;一级缓存、二级缓存 默认情况下&#xff0c;只有一级缓存开启&#xff08;sqlSession级别的缓存&#xff09;二级缓存需要手动开启配置&#xff0c;需要局域namespace级别的缓存 一级缓存&#xff08;本地缓存&#…...