C#.Net筑基-集合知识全解
01、集合基础知识
.Net 中提供了一系列的管理对象集合的类型,数组、可变列表、字典等。从类型安全上集合分为两类,泛型集合 和 非泛型集合,传统的非泛型集合存储为Object,需要类型转。而泛型集合提供了更好的性能、编译时类型安全,推荐使用。
.Net中集合主要集中在下面几个命名空间中:
1.1、集合的起源:接口关系
-
天赋技能 —— foreach:几乎所有集合都可以用
foreach
循环操作,是因为他们都继承自IEnumerable
接口,由枚举器(IEnumerator)提供枚举操作。 -
几乎所有集合都提供添加、删除、计数,来自基础接口
ICollection
、ICollection<T>
。 -
IList
、IList<T>
提供了数组的索引器、查找、插入等操作,几乎所有具体的集合类型都实现了该接口。 -
Array 是一个抽象类,是所有数组
T[]
的基类,她是类型安全的。 -
推荐尽量使用数组T[]、泛型版的集合,提供了更好的类型安全和性能。
1.2、非泛型集合—— 还有什么存在的价值?
-
非泛型的
Hashtable
,Key、Value都是Object类型的,Dictionary 是泛型版本的 Hashtable。 -
ArrayList 是非泛型版本的
List<T>
,基本很少使用,也尽量不用。
❓既然非泛型版本类型不安全,性能还差,为什么还存在呢?
主要是历史原因,泛型是.Net2.0
引入的,因此为了向后兼容,依然保留的非泛型版本集合。在接口实现时,非泛型接口一般都是显示实现的,因此基本不会用到。不过在有些场景下,非泛型接口、集合还是有点用的,如类型不固定的集合,或者用接口作为约束条件或类型判断。
ArrayList arr = new ArrayList();
arr.Add(1);
arr.Add("sam");
arr.Add(new Point());
if (arr is IList) {}class User<T> where T :IList {}
1.3、Collection<T>
、List<T>
有何不同?
❓两者比较相似,他们到底有什么区别呢?该如何选择?
-
Collection<T>
作为自定义集合基类,内部提供了一些virtual
的实现,便于继承实现自己的集合类型。其内部集合用的就是List<T>
,如下部分源码 Collection.cs。 -
List<T>
作为集合使用,是最常用的可变长集合类型了,他优化了性能,但是丢失了可扩展性,没有提供任何可以override
的成员。
public class Collection<T>
{public Collection(){items = new List<T>();}protected virtual void InsertItem(int index, T item){items.Insert(index, item);}
}
02、枚举器——foreach的秘密!
foreach
用来循环迭代可枚举对象,用一种非常简洁、优雅的姿势访问可枚举元素。常用于数组、集合,当然不仅限于集合,只要符合要求枚举要求的都可以。
2.1、IEnumerator枚举器
枚举可以foreach
枚举的密码是他们都继承自IEnumerable
接口,而更重要的是其内部的枚举器 —— IEnumerator。枚举器IEnumerator
定义了向前遍历集合元素的基本协议,其申明如下:
public interface IEnumerator
{object Current { get; }bool MoveNext();void Reset(); //这个方法是非必须的,用于重置游标,可不实现
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{new T Current { get; }
}
-
MoveNext()
移动当前元素到下一个位置,Current
获取当前元素,如果没有元素了,则MoveNext()
返回false
。注意MoveNext()
会先调用,因此首次MoveNext()
是把位置移动到第一个位置。 -
Reset()
用于重置到起点,主要用于COM互操作,使用很少,可不用实现(直接抛出 NotSupportedException)。
📢 该接口不是必须的,只要实现了公共的
Current
、无参MoveNext()
成员就可进行枚举操作。
实现一个获取偶数的枚举器:
void Main()
{var evenor = new EvenNumbersEnumerator(1, 10);while (evenor.MoveNext()){Console.WriteLine(evenor.Current); //2 4 6 8 10}
}
//获取偶数的枚举器
public struct EvenNumbersEnumerator : IEnumerator<int> //不继承IEnumerator接口,效果也是一样的
{private int _start;private int _end;private int _position = int.MinValue;public EvenNumbersEnumerator(int start, int end){_start = start;_end = end;}public int Current => _position;object IEnumerator.Current => Current; //显示实现非泛型接口,然后隐藏起来public bool MoveNext(){if (_position == int.MinValue)_position = (int.IsEvenInteger(_start) ? _start : _start + 1) - 2;_position += 2;return (_position <= _end);}public void Reset() => throw new NotSupportedException();public void Dispose() { } //IEnumerator 是实现了 IDisposable接口的
}
2.2、IEnumerable可枚举集合
IEnumerable
、IEnumerable<T>
是所有集合的基础接口,其核心方法就是 GetEnumerator()
获取一个枚举器。
public interface IEnumerable
{IEnumerator GetEnumerator();
}
public interface IEnumerable<out T> : IEnumerable
{new IEnumerator<T> GetEnumerator();
}
📢 该接口也不是必须的,只要包含
public
的“GetEnumerator()”方法也是一样的。
有了 GetEnumerator()
,就可以使用foreach
来枚举元素了,这里foreach
会被编译为 while (evenor.MoveNext()){}
形式的代码。在上面 偶数枚举器的基础上实现 一个偶数类型。
void Main()
{var evenNumber = new EvenNumbers();foreach (var n in evenNumber){Console.WriteLine(n); //2 4 6 8 10}
}
public class EvenNumbers : IEnumerable<int> //不用必须继承接口,只要有GetEnumerator()即可
{public IEnumerator<int> GetEnumerator(){return new EvenNumbersEnumerator(1, 10);}IEnumerator IEnumerable.GetEnumerator() //显示实现非泛型接口,然后隐藏起来{return GetEnumerator();}
}
foreach 迭代其实就是调用其GetEnumerator()
、Current
、MoveNext()
实现的,因此接口并不是必须的,只要有对应的成员即可。
foreach (var n in evenNumber)
{Console.WriteLine(n); //2 4 6 8 10
}
/************** 上面代码编译后的效果如下:*****************/
IEnumerator<int> enumerator = evenNumber.GetEnumerator();
try
{while (enumerator.MoveNext ()){int i = enumerator.Current;Console.WriteLine (i);}
}
finally
{if (enumerator != null){enumerator.Dispose ();}
}
2.3、yield 迭代器
yield return
是一个用于实现迭代器的专用语句,它允许你一次返回一个元素,而不是一次性返回整个集合。常来用来实现自定义的简单迭代器,非常方便,无需实现IEnumerator
接口。
🔸惰性执行:元素是按需生成的,这可以提高性能并减少内存占用(当然这个要看具体情况),特别是在处理大型集合或复杂的计算时。迭代器方法在被调用时,不会立即执行,而是在MoveNext()
时,才会执行对应yield return
的语句,并返回该语句的结果。📢Linq里的很多操作也是惰性的。
🔸简化代码:使用yield return
可以避免手动编写迭代器的繁琐过程。
🔸状态保持:yield return
自动处理状态保持,使得在每次迭代中保存当前状态变得非常简单。每一条yield return
语句执行完后,代码的控制权会交还给调用者,由调用者控制继续。
yield
迭代器方法会被会被编译为一个实现了IEnumerator
接口的私有类,可以看做是一个高级的语法糖,有一些限制(要求):
-
迭代器的返回类型可以是
IEnumerable
、IEnumerator
或他们的泛型版本。还可以用 IAsyncEnumerable<T>
来实现异步的迭代器。 -
yield break
语句提前退出迭代器,不可直接用return
,是非法的。 -
yield
语句不能和try...catch
一起使用。
void Main()
{var us = new User();foreach (string name in us){Console.WriteLine(name); //sam kwong}foreach (string name in us.GetEnumerator1()){Console.WriteLine(name); //1 sam 2}foreach (string name in us.GetEnumerator2()){Console.WriteLine(name);//KWONG}
}
public class User
{private string firstName = "sam";private string lastName = "Kwong";public IEnumerator GetEnumerator(){yield return firstName;yield return lastName;}public IEnumerable GetEnumerator1() //返回IEnumerable{Console.WriteLine("1");yield return firstName; //第一次执行到这里Console.WriteLine("2");yield break; //第二次执行到这里,也是最后一次了yield return lastName;}public IEnumerable<string> GetEnumerator2() //返回IEnumerable<string>{yield return lastName.ToUpper();}
}
03、集合!装逼了!
3.1、⭐常用集合类型
ArrayList arr2 = new ArrayList();
arr2.Add(null);
arr2.Add("sam");
arr2.Add(1);
Console.WriteLine(arr2[1]);
3.2、⭐数组Array[]
Array 数组是一种有序的集合,通过唯一索引编号进行访问。数组T[]
是最常用的数据集合了,几乎支持创建任意类型的数组。Array
是所有的数组T[]
的(隐式)基类,包括一维、多维数组。CLR会将数组隐式转换为 Array 的子类,生成一个伪类型。
-
索引从0开始。
-
定长:数组在申明时必须指定长度,超出长度访问会抛出
IndexOutOfRangeException
异常。 -
内存连续:为了高效访问,数组元素在内存中总是连续存储的。如果是值类型数组,值和数组是存储在一起的;如果是引用类型数组,则数组值存储其引用对象的(堆内存)地址。因此数组的访问是非常高效的!
-
多维数组:矩阵数组 用逗号隔开,
int[,] arr = {{1,2},{3,4}};
-
多维数组:锯齿形数组(数组的数组),
int[][] arr =new int[3][];
int[] arr = new int[100]; //申请长度100的int数组
int[] arr2 = new int[]{1,2,3}; //申请并赋值,长度为3
int[] arr3 = {1,2,3}; //同上,前面已制定类型,后面可省略
arr[1] = 1;
Console.WriteLine(arr[2]); //未赋值,默认为0
📢 几乎大部分编程语言的数组索引都是从0开始的,如C、Java、Python、JavaScript等。当然也有从1开始的,如MATLAB、R、Lua。
📢 通过上表发现,Array 的很多方法都是静态方法,而不是实例方法,这一点有点困惑,造成了使用不便。而且大部分方法都可以用Linq的扩展来代替。
3.3、Linq扩展
LINQ to Objects (C#) 提供了大量的对集合操作的扩展,可以使用 LINQ 来查询任何可枚举的集合(IEnumerable)。扩展实现主要集中在 代码 Enumerable 类(源码 Enumerable.cs),涵盖了查询、排序、分组、统计等各种功能,非常强大。
-
简洁、易读,可以链式操作,简单的代码即可实现丰富的筛选、排序和分组功能。
-
延迟执行,只有在ToList、ToArray时才会正式执行,和
yeild
一样的效果。
var arr = Enumerable.Range(1, 100).ToArray(); //生成一个数组
var evens = arr.Where(n => int.IsEvenInteger(n)); //并没有执行
var arr2 = arr.GroupBy(n => n % 10).ToArray();
04、集合的一些小技巧
4.1、集合初始化器{}
同类的初始化器类似,用{}
来初始化设置集合值,支持数组、字典。
//数组
int[] arr1 = new int[3] { 1, 2, 3 };
int[] arr2 = new int[] { 1, 2, 3 };
int[] arr4 = { 1, 2, 3 };
//字典
Dictionary<int, string> dict1 = new() { { 1, "sam" }, { 2, "william" } };
Dictionary<int, string> dict2 = new() { [5] = "sam", [6] = "zhangsan" }; //索引器写法
var dict3 = new Dictionary<int, string> { { 1, "sam" }, { 2, "william" } };
4.2、集合表达式[]
集合表达式 简化了集合的申明和赋值,直接用[]
赋值,比初始化器更简洁,语法形式和JavaScript
差不多了。可用于数组、Sapn、List,还可以自定义集合生成器。
int[] iarr1 = new int[] { 1, 2, 3, 4 }; //完整的申明方式
int[] iarr2 = { 1, 2, 3, 4 }; //前面声明有类型int[],可省略new
int[] iarr3 = [1, 2, 3, 4]; //简化版的集合表达式List<string> list = ["a1", "b1", "c1"];
Span<char> sc = ['a', 'b', 'c'];
HashSet<string> set = ["a2", "b2", "c2"];//..展开运算符,把集合中的元素展开
List<string> list2 = [.. list,..set, "ccc"]; //a1 b1 c1 a2 b2 c2 ccc
4.3、范围运算符..
a..b
表示a到b的范围(不含b),其本质是 System.Range 类型数据,表示一个索引范围,常用与集合操作。
-
可省略
a
或b
,缺省则表示到边界。 -
可结合倒数
^
使用。
int[] arr = new[] { 0, 1, 2, 3, 4, 5 };
Console.WriteLine(arr[1..3]); //1 2 //索引1、2
Console.WriteLine(arr[3..]); //3 4 5 //索引3到结尾
Console.WriteLine(arr[..]); //全部
Console.WriteLine(arr[^2..]); //4 5 //倒数到2到结尾var r = 1..3;
Console.WriteLine(r.GetType()); //System.Range
自定义的索引器也可用用范围
Range
作为范围参数。
05、提高集合性能的一些实践
🚩尽量给集合一个合适的“容量”( capacity),几乎所有可变长集合的“动态变长”其实都是有代价的。他们内部会有一个定长的“数组”,当添加元素较多(大于容量)时,就会自动扩容(如倍增),然后把原有“数组”数据拷贝(搬运)到新“数组“中。
-
因此在使用可变长集合时,尽量给一个合适的大小,可减少频繁扩容带来的性能影响。当然也不可盲目设置一个比较大的容量,这就很浪费内存空间了。
stringBuilder
也是一样的道理。 -
可变长集合的插入、删除效率都不高,因为会移动其后续元素。
下面测试一下List<T>
,当创建一个长度为1000的List
时,设置容量(1000)和不设置容量(默认4)的对比。
int max = 10000;
public void List_AutoLength(){List<int> arr = new List<int>();for (int i = 0; i < max; i++){arr.Add(i);}
}
public void List_FixedLength()
{ List<int> arr = new List<int>(max);for (int i = 0; i < max; i++){arr.Add(i);}
}
很明显,自动长度的List
速度更慢,也消耗了更多的内存。
🚩尽量不创建新数组,使用一些数组方法时需要注意尽量不要创建新的数组,如下面示例代码:
var arr = Enumerable.Range(1, 100).ToArray();
// 需求:对arr进行反序操作
var arr2 = arr.Reverse().ToArray(); //用Linq,创建了新数组
Array.Reverse(arr); //使用Array的静态方法,原地反序,没有创建新对象
比较一下上面两种反序的性能:
文章转载自:安木夕
原文链接:https://www.cnblogs.com/anding/p/18229596
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:

C#.Net筑基-集合知识全解
01、集合基础知识 .Net 中提供了一系列的管理对象集合的类型,数组、可变列表、字典等。从类型安全上集合分为两类,泛型集合 和 非泛型集合,传统的非泛型集合存储为Object,需要类型转。而泛型集合提供了更好的性能、编译时类型安全…...

AI PPT生成器,一键在线智能生成PPT工具
PPT作为商业沟通和教育培训中的重要工具,PPT制作对于我们来说并不陌生。但是传统的PPT制作不仅耗时,而且想要做出精美的PPT,需要具备一定的设计技能。下面小编就来和大家分享几款AI PPT工具,只要输入主题,内容就可以在…...

stm32学习笔记---零基础入门介绍2
目录 STM32介绍 STM32家族系列 ARM介绍 ARM内核型号种类 我们学习用的STM32 片上资源/外设(Peripheral) 命名规则 系统结构 引脚定义 STM32的启动配置 STM32最小系统电路和其他部分电路 最小系统板的实物图 附:安装软件准备 声明…...

搭建取图系统app源码开发,满足广泛应用需求
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 前言 图片已成为信息传递的重要媒介,广泛应用于各个领域。为满足日益增长的图片需求,搭建一款高效的取图系统,可以为用户提供便捷、全面的…...

语音质量评价方法之MOS
引言 在语音增强、语音合成、语音转换、声音转换、语音克隆、语音修复等等领域,常常要对输出的语音进行评价。对语音的质量评价一般关注两个方面,即主观评价和客观评价。主观评价就是人凭借听觉感受对语音进行打分,客观评价比较广泛…...

gorm简介
【1】ORM: 即Object-Relational Mapping,它的作用是在关系型数据库和对象之间作一个映射,这样我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它们就可以了。 【2】GORM gorm是go语言的一个orm…...

MySQL:SELECT list is not in GROUP BY clause 报错 解决方案
一、前言 一大早上测试环境,发现测试环境的MySQL报错了。 SELECT list is not in GROUP BY clause and contains nonaggregated column二、解决方案 官方文档中提到: 大致意思: 用于GROUP BY的SQL / 92标准要求满足以下条件: SE…...
IPython的使用技巧
1、解释说明 IPython是一个强大的Python交互式shell,它提供了丰富的功能,如自动补全、历史记录、内置帮助等。IPython使得在命令行下编写和测试Python代码变得更加方便和高效。 2、使用示例 安装IPython: pip install ipython启动IPython…...
Spring Boot 多线程例子
在Spring Boot中,多线程可以通过Java的并发工具来实现。以下是一些常见的多线程实现方法: 1. 使用Async注解和CompletableFuture: 首先,需要在Spring Boot应用的主类上添加EnableAsync注解,以启用异步支持。 java Spr…...
java干货 线程池的分析和使用
文章目录 一、了解线程池1.1 什么是线程池1.2 为什么需要线程池 二、四种线程池的使用2.1 newFixedThreadPool2.2 newCachedThreadPool2.3 newSingleThreadExecutor2.4 newScheduledThreadPool 三、自定义线程池3.1 线程池七大核心参数3.2 线程池内部处理逻辑 一、了解线程池 …...
文本张量入门
张量,英文为Tensor,是机器学习的基本构建模块,是以数字方式表示数据的形式。 张量的基本类型: 创建一个标量(0维张量),也就是一个单独的数字 scalar torch.tensor(7) scalar.ndim # 返回张量的维度 0 # …...
js文字如何轮播?
<div class"td-style"> <span class"td-text">内容1内容1内容1内容1内容1内容1</span> </div> css: <style> .td-style { width: 160px; height: 72px; overflow: hidden; white-…...

Linux 五种IO模型
注:还有一种信号驱动IO,使用较少暂不讨论; 一,区分阻塞、非阻塞和同步、异步 看了很多文章对这两组概念解释和对比,说的太复杂了,其实没必要,两句话就能说清楚。 首先,对于读数据rec…...

深度解析响应式异步编程模型
上一篇文章中我们聊了一下线程池,基于线程池的多线程编程是我们在高并发场景下提升系统处理效率的有效手段,但却不是唯一的。今天我们来看一下另一种异步开发的常用手段-响应式编程模型 传统多线程模型的缺陷 多线程模型是目前应用最为广泛的并发编程手段,但凡遇到什么性能…...

一个软件是如何开发出来的呢?
一、前言 如今,AI大爆发的时代,作为一名IT从业者,你是否也想尝试开发一套自己的系统,实现那些看似有可能实现的天马行空的想法,变成一个优秀甚至伟大的产品,甚至带来某个行业的革新,那作为一名…...
宝塔板面有哪些优势
哈喽呀,大家好呀,淼淼又来和大家见面啦,在当今数字化时代,随着云计算和互联网技术的飞速发展,服务器管理成为了许多企业和个人开发者不可或缺的一部分。然而,传统服务器管理方式的复杂性和技术门槛往往令初…...

Mybatis中BaseEntity作用
新建各种对象的时候,一般来说,有几个属性是所有对象共有的,比如说id,is_del,is_enable这些,然后设置一个基础对象,以后新建所有对象的时候都继承它,就省的每次都要写这些共有的属性了...

IDEA2023中使用run Dashboard面板?实现批量运行微服务
1、直接点击Add service--->Run Configuration Type---->Spring Boot 2、这样就出现了run Dashboard面板,可同时运行多个工程模块,shift选中所有启动类组命名(Group Configurations) 3、启动所有的项目...

分数受限,鱼和熊掌如何兼得?专业or学校,这样选最明智!
文章目录 引言一、专业解析二、名校效应分析三、好专业和好学校的权衡结论个人建议 引言 24年高考帷幕落下,一场新的思考与选择悄然来临。对于每一位高考考生,学校和专业都是开启大学新生活的两个前置必选项。但有时候“鱼与熊掌不可兼得”,…...

CentOS 8.5 - 配置ssh的免密登录
文章目录 生成ssh密钥公钥内容放入服务器 生成ssh密钥 在本地主机安装 ssh工具,并生成公钥、私钥。 # 命令行输入 ssh-keygen -r rsa# 会在当前用户的家目录下生成一个.ssh目录公钥内容放入服务器 将上一步生成的id_rsa.pub公钥的内容复制到远程服务器 # 编辑文…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...

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

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...