C#.Net筑基-集合知识全解

01、集合基础知识

.Net 中提供了一系列的管理对象集合的类型,数组、可变列表、字典等。从类型安全上集合分为两类,泛型集合 和 非泛型集合,传统的非泛型集合存储为Object,需要类型转。而泛型集合提供了更好的性能、编译时类型安全,推荐使用。

.Net中集合主要集中在下面几个命名空间中:

1.1、集合的起源:接口关系

  • 天赋技能 —— foreach:几乎所有集合都可以用foreach循环操作,是因为他们都继承自IEnumerable接口,由枚举器(IEnumerator)提供枚举操作。

  • 几乎所有集合都提供添加、删除、计数,来自基础接口 ICollectionICollection<T>

  • IListIList<T> 提供了数组的索引器、查找、插入等操作,几乎所有具体的集合类型都实现了该接口。

  • Array 是一个抽象类,是所有数组T[]的基类,她是类型安全的。

  • 推荐尽量使用数组T[]、泛型版的集合,提供了更好的类型安全和性能。

image.png

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 用来循环迭代可枚举对象,用一种非常简洁、优雅的姿势访问可枚举元素。常用于数组、集合,当然不仅限于集合,只要符合要求枚举要求的都可以。

image

image.png

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可枚举集合

IEnumerableIEnumerable<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()CurrentMoveNext()实现的,因此接口并不是必须的,只要有对应的成员即可。

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 接口的私有类,可以看做是一个高级的语法糖,有一些限制(要求):

  • 迭代器的返回类型可以是IEnumerableIEnumerator或他们的泛型版本。还可以用 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的扩展来代替。

image.png

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 类型数据,表示一个索引范围,常用与集合操作。

  • 可省略ab,缺省则表示到边界。

  • 可结合倒数^使用。

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);}
}

image.png

很明显,自动长度的List速度更慢,也消耗了更多的内存。

image.png

🚩尽量不创建新数组,使用一些数组方法时需要注意尽量不要创建新的数组,如下面示例代码:

var arr = Enumerable.Range(1, 100).ToArray();
// 需求:对arr进行反序操作
var arr2 = arr.Reverse().ToArray(); //用Linq,创建了新数组	
Array.Reverse(arr);                 //使用Array的静态方法,原地反序,没有创建新对象

比较一下上面两种反序的性能:

image.png

文章转载自:安木夕

原文链接:https://www.cnblogs.com/anding/p/18229596

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

华为数通——ACL

ACL基本介绍 ACL:访问控制列表&#xff0c;通过端口对数据流进行过滤&#xff0c;ACL判别依据是五元组&#xff1a;源IP地址&#xff0c;源端口&#xff0c;目的IP地址&#xff0c;目的端口、协议。&#xff08;ACL工作于OSI模型第三层&#xff0c;是路由器和三层交换机接口的…

SpringBoot的配置文件和YAML文件的语法

1.SpringBoot的有两种格式的全局配置文件&#xff0c;使用任何一个功能都是一样的 注意&#xff1a;SpringBoot的全局配置文件名都是固定的application.xxx ① application.properties&#xff0c; 这个是默认Spring initializr默认自动生成的配置文件&#xff0c;也是我们属…

2024免费数据恢复工具EasyRecovery电脑必备软件

&#x1f389; 数据安全小能手&#xff0c;EasyRecovery最新功能揭秘 &#x1f513; 亲爱的小红书的朋友们&#xff01;你们有没有因为不小心删除了重要文件而焦急万分&#xff1f;或者因为电脑突然崩溃&#xff0c;担心珍贵的资料丢失&#xff1f;别怕&#xff0c;今天我就来给…

岁月长河中的温柔等待

在那个年代&#xff0c;爱情往往像是一条静静流淌的小河&#xff0c;不动声色却又波澜不惊。在一个小村庄里&#xff0c;住着一对中年夫妻&#xff0c;人们叫他们李大叔和赵阿姨。他们的故事&#xff0c;就像是那个时代的缩影&#xff0c;承载着岁月的沧桑与深情的守候。 李大…

【CT】LeetCode手撕—160. 相交链表

目录 题目1- 思路2- 实现⭐160. 相交链表——题解思路 3- ACM 实现 题目 原题连接&#xff1a;160. 相交链表 1- 思路 模式识别&#xff1a;相交链表 ——> 判断是否相交 思路 保证 headA 是最长的那个链表&#xff0c;之后对其开始依次遍历 2- 实现 ⭐160. 相交链表—…

基于振弦采集仪的地下综合管廊工程安全监测技术研究

基于振弦采集仪的地下综合管廊工程安全监测技术研究 地下综合管廊工程是一项重要的城市基础设施工程&#xff0c;承载着城市供水、供电、供热、排水等重要功能。为了确保地下综合管廊工程的安全运行&#xff0c;需要进行有效的安全监测。本文将重点研究基于振弦采集仪的地下综…

Socket编程之多进程模型

一、多进程模型概述 基于最初的阻塞网络 I/O &#xff0c;若服务器要为多个客户端提供支持&#xff0c;在较为传统的手段中&#xff0c;多进程模型是常用的选择&#xff0c;即为每个客户端都分配一个进程来处理其请求。 服务器的主进程主要负责对客户连接的监听&#xff0c;一旦…

局域网共享文件夹怎么加密?方法很简单

局域网共享文件夹是企业内部信息、数据传递沟通的重要工具&#xff0c;而为了保护共享文件夹数据安全&#xff0c;我们需要使用专业的加密软件加密保护局域网共享文件夹。下面我们就来了解一下局域网共享文件夹加密方法。 局域网共享文件夹加密 在加密共享文件夹时&#xff0c…

PyCharm新手入门

前言 在之前《Python集成开发工具的选择》一文中介绍了python初学者可以使用Jupyter Notebook&#xff0c;Jupyter Notebook简单易用&#xff0c;可以用来练习代码编写&#xff0c;但是实际生产开发环境使用这个工具是远远不够用的&#xff0c;因为实际软件开发中需要软件调试…

计算机组成原理(Wrong Question)

目录 一、计算机系统概述 *1.1 计算机发展历程 1.2 计算机系统层次结构 1.3 计算机的性能指标 二、 数据的表示和运算 2.1 数制和编码 2.2 运算方法和运算电路 2.3 浮点数的表示与运算 三、存储系统 3.1 存储器概述 3.2 主存储器 3.3 主存储器与CPU的连接 3.4 外部…

MFC扩展库BCGControlBar Pro v35.0

LINK : fatal error LNK1104: 无法打开文件“BCGCBPRO2800U140.lib” BCGControlBar v25.0版本 环境VS2015&#xff0c;在运行程序时出现提示错误 &#xff1a;LINK : fatal error LNK1104: 无法打开文件“BCGCBPRO2800U140.lib” 1、需要编译一下BGCControlBar&#xff0c;在…

串口rx + RAM + LCD

REVIEW 昨天摸鱼怪发现高两位的数据写入or读出存在问题&#xff1a; RAM 串口的简单应用-CSDN博客 1. 今日摸鱼任务 UART_RX RAM LCD 来显示一下是 rx or tx 的问题 2. 代码部分 rx_ram_lcd.v module rx_ram_lcd(input clk ,input reset_n ,input uart_rx ,output …

数据结构试题 20-21

真需要就死记吧 二叉树遍历-先序(非递归)【图解代码】_哔哩哔哩_bilibili 解释一下步骤&#xff1a; 一个循环为&#xff1a; 1.取节点 2.放右子树 3.放左子树 每次循环&#xff0c;都要从栈里取出一个节点 先放右子树&#xff0c;再放左子树 那这道题就是&#xff0c;先放1&am…

计算机组成原理必备知识点

计算机组成原理必备知识点 前言 本文档由本人复习计算机组成原理期末考试所总结&#xff0c;所有习题以及知识点的页数参考2025年王道计算机组成原理 中断处理过程 硬件完成 1.关中断 2.保存断点 3.中断服务程序寻址 中断程序完成 4.保存现场和屏蔽字 5.开中断 6.执…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验2 MAC地址,IP地址,ARP协议

一、实验目的 1.掌握计算机网络的寻址问题&#xff1b; 2.验证MAC地址与IP地址的关系&#xff1b; 3.了解ARP协议的作用。 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络…

ASP.NET MVC企业级程序设计(增删,页面水平排列,字符串拼接,非空,添加框内默认提示)

目录 题目&#xff1a; 实现过程 控制器代码 DAL BLL Index Deile 题目&#xff1a; 实现过程 控制器代码 using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MvcApplication1.Models;namespac…

双通道-程控绝缘测试电阻箱的性能

双通道-程控绝缘测试电阻箱是高精度、高性能的电气测量设备&#xff0c;广泛应用于电力系统、电气设备、电子设备等领域。采用先进的数字式电阻测量技术&#xff0c;具有高精度、高稳定性的测量性能。其测量误差小于0.05%&#xff0c;能够满足各种精密测量的需求。 双通道-程控…

MacBook Air M3的电脑怎么样 新买MacBook Air提示内存不足 苹果电脑内存不够用怎么办

Apple的MacBook Air系列一直是轻薄便携笔记本电脑的代表&#xff0c;最新推出的MacBook Air M3因其出色的性能和优雅的设计而受到广泛关注。然而&#xff0c;许多用户在购买全新的MacBook Air后反应他们遇到了内存不足的提示。 本文将探讨MacBook Air M3的电脑怎么样&#xff0…

Java 集合框架:Vector、Stack 的介绍、使用、原理与源码解析

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 015 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

设计模式——观察者模式(发布/订阅模式)

观察者模式(发布/订阅模式) 是一种行为模式&#xff0c;允许你定义一种订阅机制&#xff0c;可在对象事件发生时通知多个“观察”该对象的其他对象 观察者模式定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时&am…