B.2 列表
从很多方面来说,列表是最简单也最自然的集合类型。框架中包含很多实现,具有各种功能 和性能特征。一些常用的实现在哪里都可以使用,而一些较有难度的实现则有其专门的使用场景。
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(nk),其中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 列表的更多相关文章
- ASP.NET Aries 入门开发教程8:树型列表及自定义右键菜单
前言: 前面几篇重点都在讲普通列表的相关操作. 本篇主要讲树型列表的操作. 框架在设计时,已经把树型列表和普通列表全面统一了操作,用法几乎是一致的. 下面介绍一些差距化的内容: 1:树型列表绑定: v ...
- ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑
前言: 为了赶进度,周末也写文了! 前几篇讲完查询框和工具栏,这节讲表格数据相关的操作. 先看一下列表: 接下来我们有很多事情可以做. 1:格式化 - 键值的翻译 对于“启用”列,已经配置了格式化 # ...
- ASP.NET Aries 入门开发教程3:开发一个列表页面及操控查询区
前言: Aries框架毕竟是开发框架,所以重点还是要写代码的,这样开发人员才不会失业,哈. 步骤1:新建html 建一个Html,主要有三步: 1:引入Aries.Loader.js 2:弄一个tab ...
- ASP.NET Aries 入门开发教程2:配置出一个简单的列表页面
前言: 朋友们都期待我稳定地工作,但创业公司若要躺下,也非意念可控. 若人生注定了风雨飘摇,那就雨中前行了. 最机开始看聊新的工作机会,欢迎推荐,创业公司也可! 同时,趁着自由时间,抓紧把这系列教程给 ...
- 散列表(hash table)——算法导论(13)
1. 引言 许多应用都需要动态集合结构,它至少需要支持Insert,search和delete字典操作.散列表(hash table)是实现字典操作的一种有效的数据结构. 2. 直接寻址表 在介绍散列 ...
- Python列表去重
标题有语病,其实是这样的: 假设有两个列表 : L1 = [1,2,3,4] ; L2 = [1,2,5,6] 然后去掉L1中包含的L2的元素 直接这样当然是不行的: def removeExists ...
- WPF 微信 MVVM 【续】修复部分用户无法获取列表
看过我WPF 微信 MVVM这篇文章的朋友,应该知道我里面提到了我有一个小号是无法获取列表的,始终也没找到原因. 前两天经过GitHub上h4dex大神的指导,知道了原因,是因为微信在登录以后,web ...
- Emoji选项列表
一.需要的前提文件 从网上下载Emoji的表情包,当然是png的图片,因为WPF不支持彩色的Emoji,所以,做列表的时候,需要用图片. 随着压缩包一起的还有一个Emoji.xml文件,文件的层级结构 ...
- UWP开发必备:常用数据列表控件汇总比较
今天是想通过实例将UWP开发常用的数据列表做汇总比较,作为以后项目开发参考.UWP开发必备知识点总结请参照[UWP开发必备以及常用知识点总结]. 本次主要讨论以下控件: GridView:用于显示数据 ...
- 在DevExpress程序中使用GridView直接录入数据的时候,增加列表选择的功能
在我上篇随笔<在DevExpress程序中使用Winform分页控件直接录入数据并保存>中介绍了在GridView以及在其封装的分页控件上做数据的直接录入的处理,介绍情况下数据的保存和校验 ...
随机推荐
- hdoj1106排序
/* Problem Description 输入一行数字,假设我们把这行数字中的'5'都看成空格. 那么就得到一行用空格切割的若干非负整数 (可能有些整数以'0'开头.这些头部的'0'应该被忽 ...
- FFmpeg滤镜使用指南
文件夹 1. FFmpeg滤镜文档 2. 演示样例 2.1 缩放 2.2 视频加速 2.3 滤镜图,链和滤镜关系 2.4 多个输入覆盖同一个2x2 网格 2.5 转义字符 2. ...
- python 执行shell
一.import os ex: 1.os.system('ls') ----并不能得到返回值 2.output = os.popen('ls') res = output.read() ----能得到 ...
- oc76--NSMutableDictionary
// // main.m // NSMutableDictionary // NSDictionary不可变,初始化后就不可以修改,NSMutableDictionary可变,初始化后可以改变. // ...
- Linux中grep命令的12个实践例子
grep是每个Linux发行版都预装的一个强有力的文件模式搜索工具.无论何种原因,如果你的系统没有预装它的话,你可以很容易的通过系统的包管理器来安装它(Debian/Ubuntu系中的apt-get和 ...
- 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 ...
- Mysql的简单使用(二)
接上文Mysql的简单使用(一) 字段参数以“(字段名1 数据类型1,字段名2 数据类型2,......)”的形式构建. 关于mysql常用的数据类型,一下是比较常用的几种,想查阅比较详细的资料可以自 ...
- PCB Windows Petya(永恒之蓝)勒索病毒补丁检测代码
公司内部电脑招受到新的勒索病毒Petya(永恒之蓝)攻击,直接导致受攻击的电脑系统崩贵无法启动,这次勒索病毒攻击影响范围之广,IT,人事,工程,生产,物控等部门都无一幸免,对整个公司运转产生了非常严重 ...
- E20170630-ts
displacement n. 取代,替代; 免职,停职; [船] 排水量; [化] 置换;
- C语言内存管理总结
更新: 2018/01/09 增加free() 更新: 2018/04/13 修改部分文字与表格背景色与默认颜色相同 //# TODO: malloc, alloc, calloc, realloc ...