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

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. hdoj1106排序

     /* Problem Description 输入一行数字,假设我们把这行数字中的'5'都看成空格. 那么就得到一行用空格切割的若干非负整数 (可能有些整数以'0'开头.这些头部的'0'应该被忽 ...

  2. FFmpeg滤镜使用指南

    文件夹 1. FFmpeg滤镜文档 2. 演示样例   2.1 缩放   2.2 视频加速   2.3 滤镜图,链和滤镜关系   2.4 多个输入覆盖同一个2x2 网格   2.5 转义字符   2. ...

  3. python 执行shell

    一.import os ex: 1.os.system('ls') ----并不能得到返回值 2.output = os.popen('ls') res = output.read() ----能得到 ...

  4. oc76--NSMutableDictionary

    // // main.m // NSMutableDictionary // NSDictionary不可变,初始化后就不可以修改,NSMutableDictionary可变,初始化后可以改变. // ...

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

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

  6. How to: Set Properties of Web Application Projects

    https://msdn.microsoft.com/library/aa983454(v=vs.100).aspx ASP.NET Web application projects share th ...

  7. Mysql的简单使用(二)

    接上文Mysql的简单使用(一) 字段参数以“(字段名1 数据类型1,字段名2 数据类型2,......)”的形式构建. 关于mysql常用的数据类型,一下是比较常用的几种,想查阅比较详细的资料可以自 ...

  8. PCB Windows Petya(永恒之蓝)勒索病毒补丁检测代码

    公司内部电脑招受到新的勒索病毒Petya(永恒之蓝)攻击,直接导致受攻击的电脑系统崩贵无法启动,这次勒索病毒攻击影响范围之广,IT,人事,工程,生产,物控等部门都无一幸免,对整个公司运转产生了非常严重 ...

  9. E20170630-ts

    displacement   n. 取代,替代; 免职,停职; [船] 排水量; [化] 置换;

  10. C语言内存管理总结

    更新: 2018/01/09 增加free() 更新: 2018/04/13 修改部分文字与表格背景色与默认颜色相同 //# TODO: malloc, alloc, calloc, realloc ...