首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >使用 C# 集合表达式重构代码

使用 C# 集合表达式重构代码

作者头像
郑子铭
发布于 2024-07-02 06:46:28
发布于 2024-07-02 06:46:28
30000
代码可运行
举报
运行总次数:0
代码可运行

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

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

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

使用主构造函数重构 C# 代码

https://devblogs.microsoft.com/dotnet/csharp-primary-constructors-refactoring/

集合表达式

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

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

初始化

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<char> david = [ 'D', 'a', 'v', 'i', 'd' ];

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Error CS9176: There is no target type for the collection expression
var collection = [1, 2, 3];

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

集合表达式变化

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)的存储来优化,甚至更积极地内联代码。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
string[] singleElementCollection =
[
    "one value in a collection"
];

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int[] multipleElementCollection = [1, 2, 3 /* any number of elements */];因为 Azure Open AI 的接口名称跟 Open AI 的接口名称只在于差别一个 “Azure”,因此本文读者基本只提 Azure 的接口形式。因为 Azure Open AI 的接口名称跟 Open AI 的接口名称只在于差别一个 “Azure”,因此本文读者基本只提 Azure 的接口形式。因为 Azure Open AI 的接口名称跟 Open AI 的接口名称只在于差别一个 “Azure”,因此本文读者基本只提 Azure 的接口形式。

一些历史

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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# 功能提案:字典表达式。

C# 语言参考:集合表达式 - 转换

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions#conversions

C# 功能提案:字典表达式

https://github.com/dotnet/csharplang/issues/7822

重构场景

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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”,如下视频所示:视频

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 元素语法。例如,以下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 2
            select new Query(keyValue[0], keyValue[1])
        )
        .ToList();

        return queryList;
    }
}

public record class Query(string Name, string Value);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 2
           select new Query(keyValue[0], keyValue[1])
    ];
}

列表模式

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#list-patterns

Span 和 ReadOnlySpan 支持

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Span<int> numbers = [1, 2, 3, 4, 5];
ReadOnlySpan<char> name = ['D', 'a', 'v', 'i', 'd'];

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,如下视频所示:视频

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 使用指南。

Memory 和 Span 使用指南

https://learn.microsoft.com/dotnet/standard/memory-and-spans/memory-t-usage-guidelines

语义考虑

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<int> someList = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<int> someList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

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

后续步骤

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

  • C# 功能提案:集合表达式 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/collection-expressions
  • C# 语言参考:集合表达式 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions
  • C# 文档:集合 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/collections
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C# 13 中的新增功能实操
今天大姚带领大家一起来看看 C# 13 中的新增几大功能,并了解其功能特性和实际应用场景。
追逐时光者
2025/03/31
1410
C# 13 中的新增功能实操
浅谈 C# 13 中的 params 集合
在 C# 13 中,params 的改进使其从可变数量的数组参数升级为可变数量的集合类型参数。这一改进通过支持高性能集合类型(如Span<T>,ReadOnlySpan<T>)和简化调用语法,显著提升了代码的灵活性和效率。
追逐时光者
2025/03/20
940
浅谈 C# 13 中的 params 集合
C# 12 新增功能实操!
集合表达式引入了一种新的简洁语法,用于创建常用集合值。可以使用展开运算符(..)将其他集合内联到这些值中。
追逐时光者
2024/07/25
1690
C# 12 新增功能实操!
C# 12 中新增的八大功能你都知道吗?
转眼之间C#都已经更新到了12了,那么C# 12 中新增的八大功能你都了解过吗?今天我们来简单介绍一下C# 12 中新增的八大功能。
追逐时光者
2024/02/20
3640
C# 12 中新增的八大功能你都知道吗?
C# 14 新增功能一览,你觉得实用吗?
今天咱们一起来看看在 C# 14 中新增的几个功能特性,是否给我们日常编码带了来便利。
追逐时光者
2025/05/07
1200
C# 14 新增功能一览,你觉得实用吗?
C# 中比较实用的关键字,基础高频面试题!
在C#编程中关键字是构建逻辑和实现功能的基石,它承载着编程语言的语法规则和编程智慧。熟练掌握这些基础高频关键字对提升编程能力和面试表现至关重要,它们是日常开发和解决复杂问题的关键。
追逐时光者
2025/03/22
2170
C# 中比较实用的关键字,基础高频面试题!
C#12 新功能盘点 哪个最实用?
.NET 8北京时间11月15日正式版发布,同时C#12也正式上线, 据说C#12让你的编码体验更加高效和愉快。
郑子铭
2023/11/22
3280
C#12 新功能盘点 哪个最实用?
C#12中的Collection expressions(集合表达式语法糖)
C#12中引入了新的语法糖来创建常见的集合。并且可以使用..来解构集合,将其内联到另一个集合中。
Chester Chen
2023/11/21
2310
C#12中的Collection expressions(集合表达式语法糖)
【深入浅出C#】章节 9: C#高级主题:LINQ查询和表达式
C#高级主题涉及到更复杂、更灵活的编程概念和技术,能够让开发者更好地应对现代软件开发中的挑战。其中,LINQ查询和表达式是C#高级主题中的一项关键内容,具有以下重要性和优势:
喵叔
2023/08/21
3.7K0
C#高性能开发之类型系统:从C# 7.0 到C# 14的类型系统演进全景
自C# 7.0以来,C#语言在类型系统方面引入了众多新数据类型、类型构造和语言特性,以提升性能、类型安全性和开发效率。本文全面整理了从C# 7.0到C# 14.0(截至2025年4月,C# 14.0为预览版)类型系统的新增内容,包括值元组、Span<T>、ReadOnlySpan<T>、Memory<T>、ReadOnlyMemory<T>、可空引用类型、记录、本机大小整数、记录结构、内联数组,以及其他增强(如只读结构、泛型数学支持)。
AI.NET 极客圈
2025/04/24
1450
C#高性能开发之类型系统:从C# 7.0 到C# 14的类型系统演进全景
C# as 和 is 运算符区别和用法
在C#中,as 和 is 关键字都用于处理类型转换的运算符,但它们有不同的用途和行为。本文我们将详细解释这两个运算符的区别和用法。
追逐时光者
2025/01/25
1490
C# as 和 is 运算符区别和用法
C# 探索之旅:揭秘高效学习之道
C#,这门由微软精心打造的编程语言,自诞生之日起便以其简洁的语法、强大的功能和.NET框架的深度整合,赢得了全球开发者的青睐。随着.NET的开源和跨平台发展,C#更是焕发了新的活力。今天,就让我们一起探索如何高效学习C#,掌握这门语言的精髓。
用户9127601
2024/07/11
1470
C# 探索之旅:揭秘高效学习之道
dotnet C# 分享基础 for 循环的写法
使用 for 作为循环的写法是在编程里面常用的代码写法。比如说我有一个名为 Foo 的类型,这个类型提供了一个名为 GetCount 的方法,这个方法可以缓慢的获取 Foo 里面的一个代表数量的值。且在 Foo 类型里面提供了索引器,可以根据传入的下标序号返回一个字符串,例子代码如下
林德熙
2024/08/10
1780
.NET 8.0 中有哪些新的变化?
.NET 8在整个堆栈中带来了数千项性能改进 。默认情况下会启用一种名为动态配置文件引导优化 (PGO) 的新代码生成器,它可以根据实际使用情况优化代码,并且可以将应用程序的性能提高高达 20%。现在支持的 AVX-512 指令集能够对 512 位数据向量执行并行操作,这意味着可以在更短的时间内处理更多的数据。原始类型(数字及其他类型)现在实现了新的可格式化和可解析接口,这使它们能够直接格式化和解析为 UTF-8,而无需任何转码开销。
葡萄城控件
2023/11/16
6170
.NET 8.0 中有哪些新的变化?
表达式树练习实践:C#值类型、引用类型、泛型、集合、调用函数
两种方式都是生成 ParameterExpression 类型 Parameter() 和 Variable() 都具有两个重载。他们创建一个 ParameterExpression节点,该节点可用于标识表达式树中的参数或变量。
痴者工良
2021/04/26
1.2K0
.NET 8正式发布
11 月 15 日开始的为期三天的 .NET Conf 在线活动的开幕日上,.NET 8作为微软的开源跨平台开发平台正式发布。.NET 团队着重强调云、性能、全栈 Blazor、AI 和 .NET MAUI 是.NET 8的主要亮点。。NET团队在 .NET Conf 2023 [1]活动开幕式上表示:“通过这个版本,.NET 重塑了我们构建按需扩展的智能、云原生应用程序和高流量服务的方式。 无论你是部署到 Linux 还是 Windows,使用容器还是你选择的云应用模型,.NET 8 都能更轻松地构建这些应用。”
JusterZhu
2023/11/17
8701
.NET 8正式发布
了解 C# 13 中的集合params
params关键字一直是C#中的一项重要特性,它允许开发人员向方法传递数量可变的参数,这些参数会自动封装到一个数组中。在C# 13之前,params关键字仅限于数组使用。然而,在C#的最新版本中,现在可以将params与其他集合类型一起使用了。
郑子铭
2025/02/19
2870
了解 C# 13 中的集合params
.NET周刊【6月第3期 2024-06-18】
https://www.cnblogs.com/huangxincheng/p/18243233
InCerry
2024/06/21
3500
.NET周刊【6月第3期 2024-06-18】
C# 13 Ref Struct Interfaces
C# 从 7.2 开始引入了 ref struct,ref struct 只能分配在栈上,不能被装箱,因为不能被装箱之前的版本中 ref struct 是不能实现接口的,在转成接口的时候会导致发生装箱,这是不被允许的,而我们在做一些设计的时候往往会使用到接口,用接口定义契约 contract,C# 13 开始我们可以允许 ref struct 实现接口,并且增加了可以作为泛型类型约束允许 ref struct 类型
JusterZhu
2025/01/23
1400
C# 13 Ref Struct Interfaces
C#规范整理·集合和Linq
LINQ(Language Integrated Query,语言集成查询)提供了类似于SQL的语法,能对集合进行遍历、筛选和投影。一旦掌握了LINQ,你就会发现在开发中再也离不开它。
郑子铭
2023/08/30
6410
C#规范整理·集合和Linq
推荐阅读
相关推荐
C# 13 中的新增功能实操
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验