【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);这些。此处需要介绍两个比较常用的委托(接口):Comparison,Enumerator。
Comparison
Comparison——比较器,对应的接口是IComparer,IComparer接受两个同类型变量的比较,这两个变量一个叫左值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>接口(特别注意要继承带泛型的接口而不是接受object的IComparable,避免装箱拆箱),那么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语句块外对象已经被销毁
你可能也注意到了,在编译器中提供了两个很相似的接口:IEnumerable和IEnumerator。根据词性我们知道,前者是可枚举的意思,后者是枚举器。当一个类继承了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\>为什么是神?(泛型为什么是神)一些常见,通用的委托和接口ComparisonEnumerator List<T>/LinkedList <T>为什么是神?(泛型为什么是神࿰…...
idea的断点调试
1、行断点 首先在代码的最左侧点击会显示红色的圆圈 第二步在main方法中右键选中debug run进行运行 会出现下面图片的情况 出现上图之后,点击console 下一步 这个时候就可以看到调试的结果了 6、方法调用栈:这里显示了该线程调试所经过的所有方法&…...
vue和react学哪一个比较有助于以后发展?
前言 首先声明vue和react这两个框架都是很优秀的前端框架,使用的人群下载量上数量也是相当的庞大,这篇文章没有贬低或者攻击任何一个框架的意思,只在于根据答主的问题来对这两个框架做出对比,以方便大家更加清晰的了解到当下vue和…...
【SkyWalking】分布式服务追踪与调用链系统
1、基本介绍 SkyWalking是一个开源的观测平台,官网:Apache SkyWalking; 可监控:分布式追踪调用链 、jvm内存变化、监控报警、查看服务器基本配置信息。 2、SkyWalking架构原理 在整个skywalking的系统中,有三个角色&am…...
Python“牵手”速卖通商品详情API接口运用场景及功能介绍
速卖通电商API接口是针对速卖通提供的电商服务平台,为开发人员提供了简单、可靠的技术来与速卖通电商平台进行数据交互,实现一系列开发、管理和营销等操作。其中包括商品详情API接口,通过这个API接口商家可以获取商品的详细信息,包…...
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语言】柔性数组(可边长数组)
一、介绍 柔性数组(Flexible Array),又称可变长数组。一般数组的长度是在编译时确定,而柔性数组对象的长度在运行时确定。在定义结构体时允许创建一个空数组(例如:arr [ 0 ] ),该数…...
C++信息学奥赛1131:基因相关性
这段代码的功能是比较两个字符串的相似度,并根据给定的阈值判断是否相似。 解析注释后的代码如下: #include <iostream> #include <string> using namespace std;int main() {double bf; // 定义双精度浮点数变量bf,用于存储阈…...
如何保证分布式系统中服务的高可用性:应对 ZooKeeper Leader 节点故障的注册处理策略
推荐阅读 AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画、AI讲话、翻译,GPU点亮AI想象空间 资源分享 「java、python面试题」来自UC网盘app分享,打开手机app,额外获得1T空间 https://dr…...
SQL注入之延时注入
文章目录 延时注入是什么?延时注入获取数据库版本号 延时注入是什么? 延时注入就是利用sleep()函数通过if语句判断所写的语句真假,如果为真返回我们想要的东西(例如:数据库的长度,数据库的名字等࿰…...
运维高级学习--Docker(二)
1、使用mysql:5.6和 owncloud 镜像,构建一个个人网盘。 #拉取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 单体架构 单体架构应该是我们最先接触到的架构实现了,在单体架构中使用经典的三层模型,即表现层,业务逻辑层和数据访问层。 单体架构只适合在应用初期,且访问量比较下的情况下使用,优点是性价比很…...
MyCAT命令行监控
9066端口 ,用mysql命令行连接 Mysql –utest –ptest –P9066 show help 可显示所有相关管理命令 显示后端物理库连接信息,包括当前连接数,端口 Show backend Show connection 显示当前前端客户端连接情况,已经网络流量信息、…...
【python】正则表达式匹配数据
前言 使用正则表达式处理数据,可进行字符串匹配、提取和替换等操作。在python中,通过re库完成正则匹配的操作。 一、正则语法规则 1.常用匹配符 模式描述^匹配字符串开头$匹配字符串结尾.匹配任意字符*匹配前面的字符零次或多次匹配前面的字符一次或多…...
【C++】用Windows API在控制台实现选择选项
2023年8月23日,周三上午 今天上午花了一个小时来实现这个 这个程序在碰到边界时会发出声音, 通过调用Windows API的Beep函数来实现。 #include<Windows.h> #include<conio.h> #include<iostream> #include<cstdlib>const int …...
Golang 批量执行/并发执行
提到Golang,都说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在暑假想提升一下自己,自学了go语言编程和机器学习相关学习,但是一味学习理论,终究是枯燥的,于是自己弄点小项目做。 在这之前,建议您需要掌握以下两个技…...
基于java会议室预约系统设计与实现
摘要 一个企业的发展离不开相关的规定流程。信息化到来的今天在我们的生活当中。离不开各种信息化的支持。比如钉钉会议预约、美团买菜、扫码签到等各种信息化软件。他们涉及我们生活中的方方面面给我们的生活提供了更大的便利性。大到政府、企业办公小到人们的衣食住行都离不开…...
Ubuntu18.04 交叉编译curl-7.61.0
下载 官方网址是:curl 安装依赖库 如果需要curl支持https协议,需要先交叉编译 openssl,编译流程如下: Ubuntu18.04 交叉编译openssl-1.1.1_我是谁??的博客-CSDN博客 解压 # 解压: $tar -xzvf curl-7.61.…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
云安全与网络安全:核心区别与协同作用解析
在数字化转型的浪潮中,云安全与网络安全作为信息安全的两大支柱,常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异,并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全:聚焦于保…...
