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

【C#学习笔记】数据类中常用委托及接口——以List<T>为例

在这里插入图片描述

文章目录

  • List\<T\>/LinkedList \<T\>为什么是神?(泛型为什么是神)
  • 一些常见,通用的委托和接口
    • `Comparison`
    • `Enumerator`


List<T>/LinkedList <T>为什么是神?(泛型为什么是神)

List<T>为什么是神?在谈论这个问题之前,我想先说说其他数据表结构相较于List<T>究竟差在了哪里……

首先是HashTable

本身呢就被Dictionary<TKey,TValue>完爆,HashTable既不是线程安全的,也不是类型安全的,虽然提供了Synchronized()方法可以获取线程安全的类型,以为自己是个哈希表就可以为所欲为了,但这种挑战神的行为导致最终降下了神罚,最后几乎被HasSet<T>所取代。HashSet<T>的Contains方法复杂度是O(1),List<T>的Contains方法复杂度是O(n)。HashSet还是线程安全和类型安全的。而且HashSet<T>是专门设计用来做集合运算(取交集,并集等),所以提供了UnionWith、IntersectWith等方法。无论从方方面面来看HashTable都比不上HashSet<T>

然后是Array数组类型

竟然还胆敢在神的面前跳脚,仗着自己老前辈的身份倚老卖老,连插入删除都如此困难,除了因为随机存取查找复杂度低以外一无是处。完全不适合作为存储表的对象,没有动态变长的东西!

接着是ArrayList

Array自以为换个马甲就好使了。ArrayList是类型不安全的,虽有线性表的优点,但是类型不安全,内部默认装的是object类型的,导致它存取时存在装箱拆箱的操作。没有泛型,狗都不用。

List<T>的优点

List<T>是谦卑的,虽然HashSet<T>拥有更高的效率,但是它是一个使用哈希表的集结构,不允许出现重复元素,因此和表在定位上还是有区别的,所以神不会和他计较。作为表结构,无论是线性表的List<T>还是链表的LinkedList<T>,神已经赢了太多太多了。而对于Dictionary<K,V>,神慈悲地包容了它,可以使用ToDictionary ()方法转换为Dictionary(using System.Linq)。而Dictionary中的键或者值也可以通过ToList()方法转换为List<T>

因此,线性表请认准List<T>,链表则使用LinkedList<T>,字典请使用Dictionary<K,V>。哈希表/数据集请使用HashSet<T>

只查找,首选List;
插入为主,查找和删除为辅,首选LinkedList;
删除为主,查找和插入为辅,首选Dictionary;

想要效率高,泛型不可少。不使用泛型的数据类还是往后稍稍吧。


一些常见,通用的委托和接口

在这里插入图片描述
在这里插入图片描述
List中提供了许多方法,通过方法名我们一眼就知道这些方法是干什么的了,你可能注意到了其中重载的一些用于接受接口和泛型委托的方法,例如public void Sort(Comparison<T> comparison);public void Sort(IComparer<T> comparer);这些。此处需要介绍两个比较常用的委托(接口):ComparisonEnumerator

Comparison

Comparison——比较器,对应的接口是IComparerIComparer接受两个同类型变量的比较,这两个变量一个叫左值x,一个叫右值y。
比较器的返回值是int,默认地,如果左值大于右值,那么比较器的返回值是>0的,而如果右值大于左值则返回值<0。左右值相等则等于0

在C#中list提供了这个方法:

public void Sort(Comparison<T> comparison);
public void Sort(int index, int count, IComparer<T> comparer);
public void Sort();
public void Sort(IComparer<T> comparer);

这是个用于list的默认排序方法,当我们直接调用排序的时候,将会自动地对内部数据进行升序排序:

List<int> list = new List<int>();
list.Add(1);
list.Add(3);
list.Add(2);
list.Add(6);
list.Add(4);
for (int i = 0; i < list.Count; i++)
{Debug.Log(list[i]);
}
// 输出:1 3 2 6 4
list.Sort();
for (int i = 0; i < list.Count; i++)
{Debug.Log(list[i]);
}
// 输出:1 2 3 4 6

那么如果我们想要实现降序排序怎么办呢?我们就可以使用这个委托来解决,刚才我们说,在委托中左值大于右值,那么比较器的返回值是>0的,而如果右值大于左值则返回值<0。左右值相等则等于0。那么如果我们改变了默认委托的返回值,使得左值大于右值时返回值为<0不就可以实现降序排序了吗:

void Start()
{List<int> list = new List<int>();list.Add(1);list.Add(3);list.Add(2);list.Add(6);list.Add(4);for (int i = 0; i < list.Count; i++){Debug.Log(list[i]);}// 输出:1 3 2 6 4list.Sort(Desc);for (int i = 0; i < list.Count; i++){Debug.Log(list[i]);}// 输出:6 4 3 2 1
}
public int Desc(int x, int y)
{if (x > y)//修改委托的返回值逻辑{return -1;}else{return 1;}
}

甚至我们还可以根据自己的需求来修改排序,例如我希望右值为3的时候返回0:

public int Desc(int x, int y)
{if (y == 3){return 0; //虽然可以自定义,但并无卵用,Sort方法使用的据说是快速排序加堆排序// 除非你真的很了解源码,不然最后结果是怎么样就不晓得了}if (x > y){return -1;}else{return 1;}
}
// 排序后输出: 3 6 4 2 1

同样我们也支持匿名函数,使用lambda表达式和三目运算符来实现匿名函数的最简化:

list.Sort((x, y) => { return x > y ? -1 : 1; });

那么既然List可以接受泛型,当然也能接受类,我们可否直接对类进行排序呢?答案是不行的:

public class Item
{int Money;public Item(int i){Money = i;}
}
void Start()
{List<Item> list = new List<Item>();Item item1 = new Item(1);Item item2 = new Item(3);Item item3 = new Item(2);Item item4 = new Item(6);Item item5 = new Item(4);list.Add(item1);list.Add(item2);list.Add(item3);list.Add(item4);list.Add(item5);for (int i = 0; i < list.Count; i++){Debug.Log(list[i].Money);}list.Sort();// 报错,不是可比较类型for (int i = 0; i < list.Count; i++){Debug.Log(list[i].Money);}
}

之所以无法进行比较,是因为我们所定义的这个Item类并没有继承IComparable接口,如果我们想要类可比较,有两种方法:第一种就是像我们刚才讲的,为委托重写写一个接受两个Item类型参数的返回值为int类型的函数,以比较它们的money属性:

void Start()
{/*省略部分重复代码 */list.Sort(Desc);
}
public int Desc(Item x, Item y)
{if (x.Money > y.Money){return -1;}else{return 1;}
}

但是这样的话有几个问题,首先因为函数是定义在类外的,如果需要我们比较的是一个私有变量那这个方法就不可行了;其次,把比较的方法暴露在外面也不符合我们封装的初衷。

另一个更好的做法是让类继承IComparable<T>接口(特别注意要继承带泛型的接口而不是接受objectIComparable,避免装箱拆箱),那么list就能自动传入比较的方法:

public class Item : IComparable<Item>
{int Money;public Item(int i){Money = i;}public int CompareTo(Item other){if (this.Money > other.Money){return 1;}else{return -1;}}public int GetMoney(){return this.Money;}
}
void Start()
{List<Item> list = new List<Item>();Item item1 = new Item(1);Item item2 = new Item(3);Item item3 = new Item(2);Item item4 = new Item(6);Item item5 = new Item(4);list.Add(item1);list.Add(item2);list.Add(item3);list.Add(item4);list.Add(item5);for (int i = 0; i < list.Count; i++){Debug.Log(list[i].GetMoney());}// 输出: 1 3 2 6 4list.Sort();for (int i = 0; i < list.Count; i++){Debug.Log(list[i].GetMoney());}// 输出: 1 2 3 4 6
}

使用上述的代码,我们就实现了很好的封装,既能保证money是一个私有的变量,又可以实现list中对item类的排序。


Enumerator

Enumerator——枚举器,当我们需要遍历某个数据结构的时候,往往需要用到枚举器。通常一些数据类继承了IEnumerator接口,我们可以用其中的GetEnumerator()方法来实例化这个枚举器:

IEnumerator enumerator = list.GetEnumerator();

使用枚举器可以遍历整个数据结构,其中枚举器提供了三个成员:MoveNext,Current,Reset

    public interface IEnumerator{object Current { get; }bool MoveNext();void Reset();}

当使用枚举器的时候,这样遍历:

public class Item : IComparable<Item>
{int Money;public Item(int i){Money = i;}public int CompareTo(Item other){if (this.Money > other.Money){return 1;}else{return -1;}}public int GetMoney(){return this.Money;}
}
void Start()
{Initiate();IEnumerator enumerator = list.GetEnumerator();while (enumerator.MoveNext()){Item newitem = (Item)enumerator.Current;Debug.Log(newitem.GetMoney());}enumerator.Reset();// 输出: 1 3 2 6 4
}

通过枚举器也可以实现遍历,问题在于枚举器的返回类型是object,这又避免不了装箱拆箱操作了。

我们也可以直接使用List内部提供的枚举器,这个枚举器是可以立即释放的,因为它继承了IDisposable接口:

public struct Enumerator : IEnumerator<T>, IEnumerator, IDisposable
{public T Current { get; }public void Dispose();public bool MoveNext();
}

只需使用using即可在使用完毕之后将其立即释放,实际上直接使用内部提供的枚举器反而更好,因为list内部的枚举值Current返回类型是对应的泛型而非object

using (var enumerator = list.GetEnumerator())
{while (enumerator.MoveNext()){// 由于返回值是泛型类型,所以可以直接调用方法而无需拆箱Debug.Log(enumerator.Current.GetMoney());}
}
enumerator.Reset(); // 编译错误,在using语句块外对象已经被销毁

你可能也注意到了,在编译器中提供了两个很相似的接口:IEnumerableIEnumerator。根据词性我们知道,前者是可枚举的意思,后者是枚举器。当一个类继承了IEnumerable<T>的接口时,实现的接口方法会提供GetEnumerator(),需要实现IEnumerable以及IEnumerable<T>的接口,并返回对应枚举器。当一个类继承了IEnumerable的时候,我们才可以使用foreach来进行遍历。

以下摘自「Unity3D」(6)协程使用IEnumerator的几种方式

除此之外,你可能也发现了,IEnumerator正是协程定义时的关键字,有意思的是协程的执行正是通过枚举器实现的,每个定义的单个协程其实正式的名称是Routine例程,不同Routine之间协同执行,就是Coroutine协程。这个Routine需要能够分步计算,才能够互相协作,不然一路执行到底,就是一般函数了。而IEnumerator接口恰恰承担了这个分步计算的任务。每次执行就是一次MoveNext(),并且可以通过Current返回执行中的结果。

所以,带有yield指令的IEnumerator的函数,最终会被编译成一个实现了IEnumerator接口的类,这是C#自带的功能。


经过多日对C#的学习,我已经被其深深地折服,java的特性,python的灵活,c++的花里胡哨,所有语言之主,唯一真神。厦门!🙏

相关文章:

【C#学习笔记】数据类中常用委托及接口——以List<T>为例

文章目录 List\<T\>/LinkedList \<T\>为什么是神&#xff1f;&#xff08;泛型为什么是神&#xff09;一些常见&#xff0c;通用的委托和接口ComparisonEnumerator List<T>/LinkedList <T>为什么是神&#xff1f;&#xff08;泛型为什么是神&#xff0…...

idea的断点调试

1、行断点 首先在代码的最左侧点击会显示红色的圆圈 第二步在main方法中右键选中debug run进行运行 会出现下面图片的情况 出现上图之后&#xff0c;点击console 下一步 这个时候就可以看到调试的结果了 6、方法调用栈&#xff1a;这里显示了该线程调试所经过的所有方法&…...

vue和react学哪一个比较有助于以后发展?

前言 首先声明vue和react这两个框架都是很优秀的前端框架&#xff0c;使用的人群下载量上数量也是相当的庞大&#xff0c;这篇文章没有贬低或者攻击任何一个框架的意思&#xff0c;只在于根据答主的问题来对这两个框架做出对比&#xff0c;以方便大家更加清晰的了解到当下vue和…...

【SkyWalking】分布式服务追踪与调用链系统

1、基本介绍 SkyWalking是一个开源的观测平台&#xff0c;官网&#xff1a;Apache SkyWalking&#xff1b; 可监控&#xff1a;分布式追踪调用链 、jvm内存变化、监控报警、查看服务器基本配置信息。 2、SkyWalking架构原理 在整个skywalking的系统中&#xff0c;有三个角色&am…...

Python“牵手”速卖通商品详情API接口运用场景及功能介绍

速卖通电商API接口是针对速卖通提供的电商服务平台&#xff0c;为开发人员提供了简单、可靠的技术来与速卖通电商平台进行数据交互&#xff0c;实现一系列开发、管理和营销等操作。其中包括商品详情API接口&#xff0c;通过这个API接口商家可以获取商品的详细信息&#xff0c;包…...

java调用python脚本的示例

java调用python脚本的示例 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class JavaCallPythonScript {public static void main(String[] args) {// 调用Python脚本的命令String pythonScriptPath "path/to/y…...

【C语言】柔性数组(可边长数组)

一、介绍 柔性数组&#xff08;Flexible Array&#xff09;&#xff0c;又称可变长数组。一般数组的长度是在编译时确定&#xff0c;而柔性数组对象的长度在运行时确定。在定义结构体时允许创建一个空数组&#xff08;例如&#xff1a;arr [ 0 ] &#xff09;&#xff0c;该数…...

C++信息学奥赛1131:基因相关性

这段代码的功能是比较两个字符串的相似度&#xff0c;并根据给定的阈值判断是否相似。 解析注释后的代码如下&#xff1a; #include <iostream> #include <string> using namespace std;int main() {double bf; // 定义双精度浮点数变量bf&#xff0c;用于存储阈…...

如何保证分布式系统中服务的高可用性:应对 ZooKeeper Leader 节点故障的注册处理策略

推荐阅读 AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画、AI讲话、翻译,GPU点亮AI想象空间 资源分享 「java、python面试题」来自UC网盘app分享&#xff0c;打开手机app&#xff0c;额外获得1T空间 https://dr…...

SQL注入之延时注入

文章目录 延时注入是什么&#xff1f;延时注入获取数据库版本号 延时注入是什么&#xff1f; 延时注入就是利用sleep()函数通过if语句判断所写的语句真假&#xff0c;如果为真返回我们想要的东西&#xff08;例如&#xff1a;数据库的长度&#xff0c;数据库的名字等&#xff0…...

运维高级学习--Docker(二)

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 #拉取mysql5.6和owncloud镜像 [rootlocalhost ~]# docker pull mysql:5.6 [rootlocalhost ~]# docker pull owncloud [rootlocalhost ~]# docker images REPOSITORY TAG IMAGE ID CREATED …...

QT的核心——信号与槽

目录 回顾C 语言信号 1、信号与槽 2、关联信号与槽 2.1自动关联信号与槽 2.2手动关联信号与槽 2.3断开信号与槽 3、自定义信号 3.1自定义信号使用条件 3.2自定义槽函数使用条件 4、信号与槽参数传递 4.1自定义一个带参的信号 4.2关联带参的信号与槽 4.3发送一个带…...

【业务功能篇73】web系统架构演变-单体-集群-垂直化-服务化-微服务化

1.服务架构的演 1.1 单体架构 单体架构应该是我们最先接触到的架构实现了&#xff0c;在单体架构中使用经典的三层模型&#xff0c;即表现层&#xff0c;业务逻辑层和数据访问层。 单体架构只适合在应用初期&#xff0c;且访问量比较下的情况下使用&#xff0c;优点是性价比很…...

MyCAT命令行监控

9066端口 &#xff0c;用mysql命令行连接 Mysql –utest –ptest –P9066 show help 可显示所有相关管理命令 显示后端物理库连接信息&#xff0c;包括当前连接数&#xff0c;端口 Show backend Show connection 显示当前前端客户端连接情况&#xff0c;已经网络流量信息、…...

【python】正则表达式匹配数据

前言 使用正则表达式处理数据&#xff0c;可进行字符串匹配、提取和替换等操作。在python中&#xff0c;通过re库完成正则匹配的操作。 一、正则语法规则 1.常用匹配符 模式描述^匹配字符串开头$匹配字符串结尾.匹配任意字符*匹配前面的字符零次或多次匹配前面的字符一次或多…...

【C++】用Windows API在控制台实现选择选项

2023年8月23日&#xff0c;周三上午 今天上午花了一个小时来实现这个 这个程序在碰到边界时会发出声音&#xff0c; 通过调用Windows API的Beep函数来实现。 #include<Windows.h> #include<conio.h> #include<iostream> #include<cstdlib>const int …...

Golang 批量执行/并发执行

提到Golang&#xff0c;都说Golang 天生高并发。所以分享一下我认为的Golang高并发精髓 简单的并发执行util package util import ("context""sync" )type batchRunner struct {BatchSize intctx context.Contextchannel chan func()wg sy…...

使用go语言、Python脚本搭建一个简单的chatgpt服务网站。

使用go语言、Python脚本搭建一个简单的GPT服务网站 前言 研0在暑假想提升一下自己&#xff0c;自学了go语言编程和机器学习相关学习&#xff0c;但是一味学习理论&#xff0c;终究是枯燥的&#xff0c;于是自己弄点小项目做。 在这之前&#xff0c;建议您需要掌握以下两个技…...

基于java会议室预约系统设计与实现

摘要 一个企业的发展离不开相关的规定流程。信息化到来的今天在我们的生活当中。离不开各种信息化的支持。比如钉钉会议预约、美团买菜、扫码签到等各种信息化软件。他们涉及我们生活中的方方面面给我们的生活提供了更大的便利性。大到政府、企业办公小到人们的衣食住行都离不开…...

Ubuntu18.04 交叉编译curl-7.61.0

下载 官方网址是&#xff1a;curl 安装依赖库 如果需要curl支持https协议&#xff0c;需要先交叉编译 openssl,编译流程如下&#xff1a; Ubuntu18.04 交叉编译openssl-1.1.1_我是谁&#xff1f;&#xff1f;的博客-CSDN博客 解压 # 解压&#xff1a; $tar -xzvf curl-7.61.…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

BLEU评分:机器翻译质量评估的黄金标准

BLEU评分&#xff1a;机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域&#xff0c;衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标&#xff0c;自2002年由IBM的Kishore Papineni等人提出以来&#xff0c;…...

django blank 与 null的区别

1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是&#xff0c;要注意以下几点&#xff1a; Django的表单验证与null无关&#xff1a;null参数控制的是数据库层面字段是否可以为NULL&#xff0c;而blank参数控制的是Django表单验证时字…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…...

聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇

根据 QYResearch 发布的市场报告显示&#xff0c;全球市场规模预计在 2031 年达到 9848 万美元&#xff0c;2025 - 2031 年期间年复合增长率&#xff08;CAGR&#xff09;为 3.7%。在竞争格局上&#xff0c;市场集中度较高&#xff0c;2024 年全球前十强厂商占据约 74.0% 的市场…...