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

news2024/11/6 7:17:20

在这里插入图片描述

文章目录

  • 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 4
    list.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 4
    list.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++的花里胡哨,所有语言之主,唯一真神。厦门!🙏

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

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

相关文章

STM32 进不了main 函数

1. 我用的是STM32L151C8T6 的芯片&#xff0c;在github 上找了个别人的例程&#xff0c;拿来当模板改&#xff0c;由于他用的是HSE 外部晶振&#xff0c;我用的是内部晶振HSI&#xff0c;所以需要改系统时钟&#xff0c;改完后debug&#xff0c; 一直进不了main 函数&#xff0…

Docker容器与虚拟化技术:GitHub账户注册

目录 一、实验 1.GitHub 一、实验 1.GitHub &#xff08;1&#xff09;GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;因为只支持Git作为唯一的版本库格式进行托管&#xff0c;故名GitHub。 &#xff08;2&#xff09;官网 GitHub: Let’s build from here …

206.Flink(一):flink概述,flink集群搭建,flink中执行任务,单节点、yarn运行模式,三种部署模式的具体实现

一、Flink概述 1.基本描述 Flink官网地址:Apache Flink — Stateful Computations over Data Streams | Apache Flink Flink是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。 2.有界流和无界流 无界流(流): 有定义流的开始,没有定义结束。会无休止…

Apache StreamPark系列教程第一篇——安装和体验

一、StreamPark介绍 实时即未来,在实时处理流域 Apache Spark 和 Apache Flink 是一个伟大的进步,尤其是Apache Flink被普遍认为是下一代大数据流计算引擎, 我们在使用 Flink & Spark 时发现从编程模型, 启动配置到运维管理都有很多可以抽象共用的地方, 我们将一些好的经验…

机器学习中XGBoost算法调参技巧

本文将详细解释XGBoost中十个最常用超参数的介绍&#xff0c;功能和值范围&#xff0c;及如何使用Optuna进行超参数调优。 对于XGBoost来说&#xff0c;默认的超参数是可以正常运行的&#xff0c;但是如果你想获得最佳的效果&#xff0c;那么就需要自行调整一些超参数来匹配你…

如何深入理解 Node.js 中的流(Streams)

Node.js是一个强大的允许开发人员构建可扩展和高效的应用程序。Node.js的一个关键特性是其内置对流的支持。流是Node.js中的一个基本概念&#xff0c;它能够实现高效的数据处理&#xff0c;特别是在处理大量信息或实时处理数据时。 在本文中&#xff0c;我们将探讨Node.js中的流…

es和数据库同步方案

5.5 课程信息索引同步 5.5.1 技术方案 通过向索引中添加课程信息最终实现了课程的搜索&#xff0c;我们发现课程信息是先保存在关系数据库中&#xff0c;而后再写入索引&#xff0c;这个过程是将关系数据中的数据同步到elasticsearch索引中的过程&#xff0c;可以简单成为索引…

SD-WebUI和ComfyUI的局域网访问设置!

如何通过局域网访问AI绘画软件&#xff0c;这是星球成员提的一个问题&#xff0c;而且两个软件都问到了&#xff0c;我也回答过了。现在把内容整理一下发出来&#xff0c;大家可能用得着。 SD-WebUI和ComfyUI这两个AI绘画工具都是通过浏览器来使用&#xff0c;但是默认情况下并…

oops Framwork creator游戏开发框架

环境&#xff1a; Mac oops Framework 该框架是由gdflas编写&#xff0c;基于cocosCreator 3.x 而实现的开源游戏框架。特点&#xff1a; 框架通过插件方式提供&#xff0c;与项目相分离&#xff0c;方便不同版本平滑升级内置模块低耦合&#xff0c; 可根据需要进行删减&…

如何使用HTML5新增的标签来构建语义化的页面结构?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ <header>&#xff1a;⭐ <nav>&#xff1a;⭐ <main>&#xff1a;⭐ <section>&#xff1a;⭐ <article>&#xff1a;⭐ <aside>&#xff1a;⭐ <footer>&#xff1a;⭐ <figure> 和 &l…

开黑啦kook 机器人开发 PHP swoole Liunx 服务器(宝塔)

安装环境 PHP 拓展 直接使用 宝塔一键安装 &#xff08;Windows系统不支持&#xff09; 设置命令行的PHP版本避免执行脚本时 获取不到 swoole 检查swoole是否安装成功 获取官方SDK GitHub - kaiheila/php-bot: 开黑啦机器人的php版本https://github.com/kaiheila/php-bot 配…

同态排序算法

参考文献&#xff1a; [Batcher68] Batcher K E. Sorting networks and their applications[C]//Proceedings of the April 30–May 2, 1968, spring joint computer conference. 1968: 307-314. [SV11] Smart, N.P., Vercauteren, F.: Fully homomorphic SIMD operations. IA…

智能井盖传感器,物联网智能井盖系统

随着城市人口的不断增加和城市化进程的不断推进&#xff0c;城市基础设施的安全和可靠性变得愈发重要&#xff0c;城市窨井盖作为城市基础设施重要组成部分之一&#xff0c;其安全性事关城市安全有序运行和居民生产生活安全保障。 近年来&#xff0c;各地都在加强城市窨井盖治理…

多页面应用多次引入同一个资源优化方法

介绍 项目是多页面应用&#xff0c;每个界面都会引入一次layui框架源码&#xff0c;造成未优化之前界面加载十分缓慢 优化探索 想办法让多页面只加载一次 但是由于多页面使用iframe&#xff0c;主页面和子页面资源隔离&#xff0c;无法让资源只加载一次 利用浏览器缓存 在…

为Claude的分析内容做准备:提取PDF页面内容的简易应用程序

由于Claude虽然可以分析整个文件&#xff0c;但是对文件的大小以及字数是有限制的&#xff0c;为了将pdf文件分批传入Claude人工智能分析和总结文章内容&#xff0c;才有了这篇博客&#xff1a; 在本篇博客中&#xff0c;我们将介绍一个基于 wxPython 和 PyMuPDF 库编写的简易的…

计算机安全学习笔记(I):访问控制安全原理

访问控制原理 从广义上来讲&#xff0c;所有的计算机安全都与访问控制有关。 RFC 4949: Internet Security Glossary, Version 2 (rfc-editor.org) RFC 4949 定义的计算机安全&#xff1a;用来实现和保证计算机系统的安全服务的措施&#xff0c;特别是保证访问控制服务的措施…

【Flink】Flink提交流程

我们通常在学习的时候需要掌握大数据组件的原理以便更好的掌握这个大数据组件&#xff0c;Flink实际生产开发过程中最常见的就是提交到yarn上进行调度&#xff0c;模式使用的Per-Job模式&#xff0c;下面我们就给大家讲下Flink提交Per-Job任务到yarn上的流程&#xff0c;流程图…

【图论】缩点的综合应用(一)

一.缩点的概念 缩点&#xff0c;也称为点缩法&#xff08;Vertex Contraction&#xff09;&#xff0c;是图论中的一种操作&#xff0c;通常用于缩小图的规模&#xff0c;同时保持了图的某些性质。这个操作的目标是将图中的一些节点合并为一个超级节点&#xff0c;同时调整相关…

springboot小知识:配置feign服务超时时间

背景&#xff1a;当前项目通过feign服务调用了其他两个项目的接口&#xff0c;但是由于特殊需求&#xff0c;需要调整某一个项目的feign服务的默认超时时间&#xff1a; 默认连接超时10秒&#xff0c;默认读取超时时间 60秒 1.找到定义的FeignClient 2.根据FeignClient定义的名…

基于YOLOV8模型的课堂场景下人脸目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOV8模型的课堂场景下人脸目标检测系统可用于日常生活中检测与定位课堂场景下人脸&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检…