从很多方面来说,列表是最简单也最自然的集合类型。框架中包含很多实现,具有各种功能 和性能特征。一些常用的实现在哪里都可以使用,而一些较有难度的实现则有其专门的使用场景。

B.2.1  List<T>

  在大多数情况下, List<T> 都是列表的默认选择。它实现了 IList<T> ,因此也实现了 ICollection<T> 、 IEnumerable<T> 和 IEnumerable 。此外,它还实现了非泛型的 ICollection 和 IList 接口,并在必要时进行装箱和拆箱,以及进行执行时类型检查,以保证新元素始终与 T 兼容。

   List<T> 在内部保存了一个数组,它跟踪列表的逻辑大小和后台数组的大小。向列表中添加 元素,在简单情况下是设置数组的下一个值,或(如果数组已经满了)将现有内容复制到新的更 大的数组中,然后再设置值。这意味着该操作的复杂度为O(1)或O(n),取决于是否需要复制值。 扩展策略没有在文档中指出,因此也不能保证——但在实践中,该方法通常可以扩充为所需大小 的两倍。这使得向列表末尾附加项为O(1)平摊复杂度(amortized complexity);有时耗时更多,但 这种情况会随着列表的增加而越来越少。

  你可以通过获取和设置 Capacity 属性来显式管理后台数组的大小。 TrimExcess 方法可以 使容量等于当前的大小。实战中很少有必要这么做,但如果在创建时已经知道列表的实际大小, 则可将初始的容量传递给构造函数,从而避免不必要的复制。

   从 List<T> 中移除元素需要复制所有的后续元素,因此其复杂度为O(nk),其中k为移除元 素的索引。从列表尾部移除要比从头部移除廉价得多。另一方面,如果要通过值移除元素而不是 索引(通过 Remove 而不是 RemoveAt ),那么不管元素位置如何复杂度都为O(n):每个元素都将 得到平等的检查或打乱。

   List<T> 中的各种方法在一定程度上扮演着LINQ前身的角色。 ConvertAll 可进行列表投影; FindAll 对原始列表进行过滤,生成只包含匹配指定谓词的值的新列表。 Sort 使用类型默认的或 作为参数指定的相等比较器进行排序。但 Sort 与LINQ中的 OrderBy 有个显著的不同: Sort 修改原 始列表的内容,而不是生成一个排好序的副本。并且, Sort 是不稳定的,而 OrderBy 是稳定的; 使用 Sort 时,原始列表中相等元素的顺序可能会不同。LINQ不支持对 List<T> 进行二进制搜索: 如果列表已经按值正确排序了, BinarySearch 方法将比线性的 IndexOf 搜索效率更高 ① 。

  List<T> 中略有争议的部分是 ForEach 方法。顾名思义,它遍历一个列表,并对每个值都执行 某个委托(指定为方法的参数)。很多开发者要求将其作为 IEnumerable<T> 的扩展方法,但却一 直没能如愿;Eric Lippert在其博客中讲述了这样做会导致哲学麻烦的原因(参见http://mng.bz/Rur2)。 在我看来使用Lambda表达式调用 ForEach 有些矫枉过正。另一方面,如果你已经拥有一个要为列 表中每个元素都执行一遍的委托,那还不如使用 ForEach ,因为它已经存在了。① 二进制搜索的复杂度为O(log n),线性搜索为O(n)。

B.2.2 数组

  在某种程度上,数组是.NET中最低级的集合。所有数组都直接派生自 System.Array ,也是 唯一的CLR直接支持的集合。一维数组实现了 IList<T> (及其扩展的接口)和非泛型的 IList 、 ICollection 接口;矩形数组只支持非泛型接口。数组从元素角度来说是易变的,从大小角度 来说是固定的。它们显示实现了集合接口中所有的可变方法(如 Add 和 Remove ),并抛出 NotSupportedException 。

   引用类型的数组通常是协变的;如 Stream[] 引用可以隐式转换为 Object[] ,并且存在显式 的反向转换 ① 。这意味着将在执行时验证数组的改变——数组本身知道是什么类型,因此如果先 将 Stream[] 数组转换为 Object[] ,然后再试图向其存储一个非 Stream 的引用,则将抛出 ArrayTypeMismatchException 。

   CLR包含两种不同风格的数组。向量是下限为0的一维数组,其余的统称为数组(array)。向 量的性能更佳,是C#中最常用的。 T[][] 形式的数组仍然为向量,只不过元素类型为 T[] ;只有 C#中的矩形数组,如 string[10, 20] ,属于CLR术语中的数组。在C#中,你不能直接创建非 零下限的数组——需要使用 Array.CreateInstance 来创建,它可以分别指定下限、长度和元 素类型。如果创建了非零下限的一维数组,就无法将其成功转换为 T[] ——这种强制转换可以通 过编译,但会在执行时失败。

  C#编译器在很多方面都内嵌了对数组的支持。它不仅知道如何创建数组及其索引,还可以在 foreach 循环中直接支持它们;在使用表达式对编译时已知为数组的类型进行迭代时,将使用 Length 属性和数组索引器,而不会创建迭代器对象。这更高效,但性能上的区别通常忽略不计。

  与 List<T> 相同,数组支持 ConvertAll 、 FindAll 和 BinarySearch 方法,不过对数组来 说,这些都是 Array 类的以数组为第一个参数的静态方法。

  回到本节最开始所说的,数组是相当低级的数据结构。它们是其他集合的重要根基,在适当 的情况下有效,但在大量使用之前还是应该三思。Eric同样为该话题撰写了博客,指出它们有“些 许害处”(参见http://mng.bz/3jd5)。我不想夸大这一点,但在选择数组作为集合类型时,这是一 个值得注意的缺点。

  ① 容易混淆的是,也可以将 Stream[] 隐式转换为 IList<Object> ,尽管 IList<T> 本身是不变的。

B.2.3  LinkedList<T>

  什么时候列表不是list呢?答案是当它为链表的时候。 LinkedList<T> 在很多方面都是一个 列表,特别的,它是一个保持项添加顺序的集合——但它却没有实现 IList<T> 。因为它无法遵 从通过索引进行访问的隐式契约。它是经典的计算机科学中的双向链表:包含头节点和尾节点, 每个节点都包含对链表中前一个节点和后一个节点的引用。每个节点都公开为一个 LinkedListNode<T> ,这样就可以很方便地在链表的中部插入或移除节点。链表显式地维护其 大小,因此可以访问 Count 属性。

  在空间方面,链表比维护后台数组的列表效率要低,同时它还不支持索引操作,但在链表中的 任意位置插入或移除元素则非常快,前提是只要在相关位置存在对该节点的引用。这些操作的复杂 度为O(1),因为所需要的只是对周围的节点修改前/后的引用。插入或移除头尾节点属于特殊情况, 通常可以快速访问需要修改的节点。迭代(向前或向后)也是有效的,只需要按引用链的顺序即可。

  尽管 LinkedList<T> 实现了 Add 等标准方法(向链表末尾添加节点),我还是建议使用显式 的 AddFirst 和 AddLast 方法,这样可以使意图更清晰。它还包含匹配的 RemoveFirst 和 RemoveLast 方法,以及 First 和 Last 属性。所有这些操作返回的都是链表中的节点而不是节点 的值;如果链表是空(empty)的,这些属性将返回空(null)。

B.2.4 Collection<T> 、 BindingList<T> 、 ObservableCollection<T> 和 KeyedCollection<TKey, TItem>

  Collection<T> 与我们将要介绍的剩余列表一样,位于 System.Collections.Object- Model 命名空间。与 List<T> 类似,它也实现了泛型和非泛型的集合接口。

   尽管你可以对其自身使用 Collection<T> ,但它更常见的用法是作为基类使用。它常扮演 其他列表的包装器的角色:要么在构造函数中指定一个列表,要么在后台新建一个 List<T> 。所 有对于集合的变动行为,都通过受保护的虚方法( InsertItem 、 SetItem 、 RemoveItem 和 ClearItems )实现。派生类可以拦截这些方法,引发事件或提供其他自定义行为。派生类可通 过 Items 属性访问被包装的列表。如果该列表为只读,公共的变动方法将抛出异常,而不再调用 虚方法,你不必在覆盖的时候再次检查。 BindingList<T> 和 ObservableCollection<T> 派生自 Collection<T> ,可以提供绑定 功能。

  BindingList<T> 在.NET 2.0中就存在了,而 ObservableCollection<T> 是WPF (Windows Presentation Foundation)引入的。当然,在用户界面绑定数据时没有必要一定使用它 们——你也许有自己的理由,对列表的变化更有兴趣。这时,你应该观察哪个集合以更有用的方 式提供了通知,然后再选择使用哪个。注意,只会通知你通过包装器所发生的变化;如果基础列 表被其他可能会修改它的代码共享,包装器将不会引发任何事件。

   KeyedCollection<TKey, TItem> 是列表和字典的混合产物,可以通过键或索引来获取项。 与普通字典不同的是,键不能独立存在,应该有效地内嵌在项中。在许多情况下,这很自然,例 如一个拥有 CustomerID 属性的 Customer 类型。 KeyedCollection<,> 为抽象类;派生类将实 现 GetKeyForItem 方法,可以从列表中的任意项中提取键。在我们这个客户的示例中, GetKeyForItem 方法返回给定客户的ID。与字典类似,键在集合中必须是唯一的——试图添加 具有相同键的另一个项将失败并抛出异常。尽管不允许空键,但 GetKeyForItem 可以返回空(如 果键类型为引用类型),这时将忽略键(并且无法通过键获取项)。

B.2.5  ReadOnlyCollection<T> 和 ReadOnlyObservableCollection<T>

  最后两个列表更像是包装器,即使基础列表为易变的也只提供只读访问。它们仍然实现了泛型和非泛型的集合接口。并且混合使用了显式和隐式的接口实现,这样使用具体类型的编译时表达式的调用者将无法使用变动操作。

  ReadOnlyObservableCollection<T> 派生自 ReadOnlyCollection<T> ,并和 Observerble- Collection<T> 一样实现了相同的 INotifyCollectionChanged 和 INotifyPro pertyChanged 接口。 ReadOnlyObservableCollection<T> 的实例只能通过一个 ObservableCollection<T> 后台列表进行构建。尽管集合对调用者来说依然是只读的,但它们可以观察对后台列表其他地方的 改变。

  尽管通常情况下我建议使用接口作为API中方法的返回值,但特意公开 ReadOnly- Collection<T> 也是很有用的,它可以为调用者清楚地指明不能修改返回的集合。但仍需写明 基础集合是否可以在其他地方修改,或是否为有效的常量。

B.2 列表的更多相关文章

  1. ASP.NET Aries 入门开发教程8:树型列表及自定义右键菜单

    前言: 前面几篇重点都在讲普通列表的相关操作. 本篇主要讲树型列表的操作. 框架在设计时,已经把树型列表和普通列表全面统一了操作,用法几乎是一致的. 下面介绍一些差距化的内容: 1:树型列表绑定: v ...

  2. ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑

    前言: 为了赶进度,周末也写文了! 前几篇讲完查询框和工具栏,这节讲表格数据相关的操作. 先看一下列表: 接下来我们有很多事情可以做. 1:格式化 - 键值的翻译 对于“启用”列,已经配置了格式化 # ...

  3. ASP.NET Aries 入门开发教程3:开发一个列表页面及操控查询区

    前言: Aries框架毕竟是开发框架,所以重点还是要写代码的,这样开发人员才不会失业,哈. 步骤1:新建html 建一个Html,主要有三步: 1:引入Aries.Loader.js 2:弄一个tab ...

  4. ASP.NET Aries 入门开发教程2:配置出一个简单的列表页面

    前言: 朋友们都期待我稳定地工作,但创业公司若要躺下,也非意念可控. 若人生注定了风雨飘摇,那就雨中前行了. 最机开始看聊新的工作机会,欢迎推荐,创业公司也可! 同时,趁着自由时间,抓紧把这系列教程给 ...

  5. 散列表(hash table)——算法导论(13)

    1. 引言 许多应用都需要动态集合结构,它至少需要支持Insert,search和delete字典操作.散列表(hash table)是实现字典操作的一种有效的数据结构. 2. 直接寻址表 在介绍散列 ...

  6. Python列表去重

    标题有语病,其实是这样的: 假设有两个列表 : L1 = [1,2,3,4] ; L2 = [1,2,5,6] 然后去掉L1中包含的L2的元素 直接这样当然是不行的: def removeExists ...

  7. WPF 微信 MVVM 【续】修复部分用户无法获取列表

    看过我WPF 微信 MVVM这篇文章的朋友,应该知道我里面提到了我有一个小号是无法获取列表的,始终也没找到原因. 前两天经过GitHub上h4dex大神的指导,知道了原因,是因为微信在登录以后,web ...

  8. Emoji选项列表

    一.需要的前提文件 从网上下载Emoji的表情包,当然是png的图片,因为WPF不支持彩色的Emoji,所以,做列表的时候,需要用图片. 随着压缩包一起的还有一个Emoji.xml文件,文件的层级结构 ...

  9. UWP开发必备:常用数据列表控件汇总比较

    今天是想通过实例将UWP开发常用的数据列表做汇总比较,作为以后项目开发参考.UWP开发必备知识点总结请参照[UWP开发必备以及常用知识点总结]. 本次主要讨论以下控件: GridView:用于显示数据 ...

  10. 在DevExpress程序中使用GridView直接录入数据的时候,增加列表选择的功能

    在我上篇随笔<在DevExpress程序中使用Winform分页控件直接录入数据并保存>中介绍了在GridView以及在其封装的分页控件上做数据的直接录入的处理,介绍情况下数据的保存和校验 ...

随机推荐

  1. 高效开发之SASS篇 灵异留白事件——图片下方无故留白 你会用::before、::after吗 link 与 @import之对比 学习前端前必知的——HTTP协议详解 深入了解——CSS3新增属性 菜鸟进阶——grunt $(#form :input)与$(#form input)的区别

    高效开发之SASS篇   作为通往前端大神之路的普通的一只学鸟,最近接触了一样稍微高逼格一点的神器,特与大家分享~ 他是谁? 作为前端开发人员,你肯定对css很熟悉,但是你知道css可以自定义吗?大家 ...

  2. 使用引导扇区维护工具BOOTICE编辑系统启动列表BCD文件

    使用引导扇区维护工具BOOTICE编辑系统启动列表BCD文件 系列文章: 笔记本电脑提速之加装内存条.SSD固态硬盘.光驱位换SSD固态硬盘 笔记本ThinkPad E430c加装内存和SSD固态硬盘 ...

  3. 【Spark】DAGScheduler源代码浅析

    DAGScheduler DAGScheduler的主要任务是基于Stage构建DAG,决定每个任务的最佳位置 记录哪个RDD或者Stage输出被物化 面向stage的调度层.为job生成以stage ...

  4. Magento 模块开发之DispatchEvent

    在这一章节中.我们来了解 Magento 中的事件分发机制 Mage::dispatchEvent() 在创建自己的模块时, Event 事件的分发将会变成十分实用且有效 以个人的经验. 事件的分发使 ...

  5. Script Library 配置 和 使用

    Script Library有两个级别,Workspace级别和Project级别 使用:这里的package指的是Script Library下的文件夹名,和引用代码里的package没有关系

  6. Linux中grep命令的12个实践例子

    grep是每个Linux发行版都预装的一个强有力的文件模式搜索工具.无论何种原因,如果你的系统没有预装它的话,你可以很容易的通过系统的包管理器来安装它(Debian/Ubuntu系中的apt-get和 ...

  7. Kaggle "Microsoft Malware Classification Challenge"——就是沙箱恶意文件识别,有 Opcode n-gram特征 ASM文件图像纹理特征 还有基于图聚类方法

    使用图聚类方法:Malware Classification using Graph Clustering 见 https://github.com/rahulp0491/Malware-Classi ...

  8. bzoj3240 [Noi2013]矩阵游戏——费马小定理+推式子

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3240 n 和 m 太过巨大,不难想到应该用费马小定理什么的来缩小范围: 总之就是推式子啦,看 ...

  9. PCB MS SQL 标量函数与表值函数(CLR) 实现文件与目录操作

    一.C#写SQL SERVER(CLR)实现文件操作 标量函数: 文件移动 ,复制,检测文件存在,写入新文件文本,读取文本,创建目录,删除目录,检测目录是否存在 /// <summary> ...

  10. PCB javascript解析钻孔(Excellon)格式实现方法

    解析钻孔(Excellon)格式前首先得了解此格式,这样才能更好的解析呀. 一个钻孔里面包含的基本信息如下: 1.单位:公式mm,英制inch 2.省零方式:前省零,后省零 3.坐标方式:绝对坐标,相 ...