博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
十六、C# 常用集合类及构建自定义集合(使用迭代器)
阅读量:7122 次
发布时间:2019-06-28

本文共 10845 字,大约阅读时间需要 36 分钟。

常用集合类及构建自定义集合
1、更多集合接口:IList<T>、IDictionary<TKey,TValue>、IComparable<T>、ICollection<T>
2、主要集合类:List<T>、IDictionary<TKey,TValue>、SortedDictionary<TKey,TValue>和SortedList<T>
       Stack<T>、Queue<T>、LinkedList<T>
3、提供一个索引运算符
4、返回null或者空集合
5、迭代器:定义、语法、yield、状态、yieldbreak
 
有两种与集合相关的类:支持泛型的和不支持泛型的。
 
一、更多集合接口
IEnumerable ICollection IEnumerable<T>
IList IDictionary ICollection<T>
IList<T> IDictionary<TKey,TValue>
 
1、IList<T>与IDictonary<TKey,TValue>
 
挑选一个集合类时,首先要考虑的两个接口就是IList<T>和IDictionary<TKey,TValue>。
这两个接口决定了集合类型是侧重于通过索引来获取值,还是侧重于通过键来获取值。
前者用于支持通过索引来获取值,后者以键为中心。
 
 
2、IComparable<T>
此接口是实现排序的关键,该接口有一个CompareTo()方法,它返回一个整数,指出传递
的元素是大于、小于还是等于当前元素。为此键的数据类型必须实现此接口。
 
用IComparer<T>排序:
为了实现自定义排序,另一个方法是向排序方法传递实现了IComparer<T>的元素。
但是,集合中的元素通常不直接支持这个接口。
 
 
3、ICollection<T>
IList<T>和IDictionary<TKey,TValue>都实现了ICollection<T>。
此接口是从IEnumerable<T>派生的,并包含两个成员:Count属性和CopyTo()。
 
 
二、主要集合类
 
共5类关键的集合类,它们的区别在于数据的插入,存储以及获取的方式不同。
所有泛型类都位于System.Collections.Generic命名空间,等价的非泛型版本位于System.Collections命名空间
1、列表集合:List<T>
具有与数组相似的属性,关键的区别在于随着元素数量的增大,这此列表类也会自动扩展。
除此之外,列表可以通过显式调用TrimToSize(0或Capacity来缩小。
 
实现了接口:Ilist<T> ICollection<T> IEnumerable<T> IList ICollection IEnumerable
 
其显著功能在于每个元素都可以根据索引来单独访问。
 
 
2、字典集合:Dictionary<TKey,TValue>
和列表集合不同,字典类存储的是"名称/值"对(键值对)。
名称相当于一个独一无二的键,可以利用它像在数据库中利用主键来访问一条记录那样查找对应的元素。
 
 
3、已排序集合:SortedDictionary<TKey,TValue>和SortedList<T>
已排序集合类中的元素是已经排好序的。
 
4、栈集合:Stack<T>
栈集合类被设计成后入先出(LIFO)集合。
两个关键的方法是Push()和Pop()
 
 
5、队列集合:Queue<T>
遵循的是先入先出(FIFO)排序模式。
Enqueue()  DEqueue()方法用来入队和出队
 
6、链表:LinkedList<T>
它允许正向和反向遍历。
 
三、提供一个索引运算符
 
索引运算符是一对方括号,它通常用于索引一个集合。
 
四、返回Null或者空集合
 
 
五、迭代器
 
利用迭代器为自定义集合实现自己的IEnumerator<T>接口和非泛型IEnumerator接口。
迭代器用清楚的语法描述了如何循环遍历(也就是迭代)集合类中的数据 ,尤其是如何使用
foreach来迭代。
在迭代器的帮助下,最终用户可以遍历一个集合的内部结构,同时不必了解那个结构。
 
1、迭代器的定义
 
迭代器是实现类的方法的一个途径,它们是更加复杂的枚举数模式的一种语法简写形式。
C#编译器遇到一个迭代器时,会把它的内容扩展成实现了枚举数模式的CIL代码。因此实现迭代器
对“运行时”没有特别的依赖。当然,因为CIL仍然采用的是枚举数模式 ,所以使用迭代器之后,
并不会带来真正的运行时性能优势。只是能显著提高程序员的编程效率。
2、迭代器语法
迭代器提供了迭代器接口(也就是IEnumerator<T>接口和非泛型IEnumerator接口的组合)的一个快捷实现。
1   2     public class BinaryTree
: IEnumerable
3 { 4 5 public BinaryTree(T value) 6 { } 7 public IEnumerator
GetEnumerator() 8 { 9 10 }11 public T Value12 {13 get { return _value; }14 set { _value = value; }15 }16 private T _value;17 18 }19 public struct Pair
20 {21 private T _first;22 private T _second;23 public T First24 {25 get { return _first; }26 set { _first = value; }27 }28 public T Second29 {30 get { return _second; }31 set { _second = value; }32 }33 public Pair(T first,T second)34 {35 _first = first;36 _second = second;37 38 }39 }

 

 
3、从迭代器生成值
迭代器类似于函数,但不是返回(return)值,而是生成值。
在BinaryTree<T>的情况下,迭代器的生成类型对应于参数T。
假如使用的是IEnumerator的非泛型版本,那么返回类型就是object。
为了正确实现迭代器模式,需要维护一个内部状态机,以便在枚举集合
的时候跟踪记录当前位置 。
在BinaryTree<T>的情况下,要跟踪记录的是树中的哪些元素已被枚举,
哪些还没有被枚举。
 
迭代器用内置的状态机去跟踪当前和下一个元素。
迭代器每次遇到新的yield return语句时,都会返回修正。然后,当下一次迭代
开始的时候,会紧接在上一个yield return语句之后执行。
1     class Program 2     { 3         static void Main(string[] args) 4         { 5             CSharpPrimitiveTypes primitives = new CSharpPrimitiveTypes(); 6             foreach (string primitive in primitives) 7             { 8                 Console.WriteLine(primitive); 9             }10  11             Console.ReadLine();12  13  14         }15  16  17     }18  19     public class CSharpPrimitiveTypes : IEnumerable
20 {21 public IEnumerator
GetEnumerator()22 {23 yield return "object";24 yield return "byte";25 yield return "uint";26 yield return "ulong";27 yield return "float";28 yield return "char";29 yield return "bool";30 yield return "string";31 }32 33 //因为System.Collections.Generic.IEnumerable
继承34 //System.Collections.IEnumerable这个接口,所以也需要实现35 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()36 {37 return GetEnumerator();38 }39 }

 

 
输出:
object
byte
uint
ulong
float
char
bool
string
 
4、迭代器和状态
一个迭代器在foreach语句中被首次调用时,它的状态会在枚举器中初始化。
只要foreach语句在调用点持续执行,迭代器就会一直维护其状态。
当你生成一个值,处理它,并恢复foreach语句执行时,迭代器会从上一次离开的位置继续处理。
当foreach语句终止时,迭代器的状态就不再保存。
总是可以安全地调用迭代器,因为生成的代码永远不会重置迭代器的状态,而是每次需要时都会个新的迭代器。
 
5、更多的迭代器例子
在修改BinaryTree<T>之前,必须修改Pair<T>,让它支持使用迭代器的IEnumerable<T>接口。
1     class Program 2     { 3         static void Main(string[] args) 4         { 5             Pair
fullname = new Pair
("xxm", "yyp"); 6 foreach (string name in fullname) 7 { 8 Console.WriteLine(name); 9 }10 11 Console.ReadLine();12 13 14 }15 16 17 }18 19 20 public struct Pair
: IEnumerable
21 {22 private T _first;23 private T _second;24 public T First25 {26 get { return _first; }27 set { _first = value; }28 }29 public T Second30 {31 get { return _second; }32 set { _second = value; }33 }34 public Pair(T first, T second)35 {36 _first = first;37 _second = second;38 }39 #region IEnumerable
40 public IEnumerator
GetEnumerator()41 {42 yield return First;43 yield return Second;44 }45 #endregion46 #region IEnumerable Members47 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()48 {49 return GetEnumerator();50 }51 #endregion52 }53

 

 
System.Collections.Generic.IEnumerable<T>是从System.Collections.IEnumerable继承的。
因此,在实现IEnumerable<T>的时候,还有必要实现IEnumerable。
这个实现是显式进行的。
两个至少有一个是显式实现的。
 
6、将yield return语句放到循环中
 
使用yield return语句,可以从一个循环结构中返回值。
 
 
1     class Program  2     {  3         static void Main(string[] args)  4         {  5             BinaryTree
jfkFamilyTree = new BinaryTree
("root");//根节点 6 7 jfkFamilyTree.Subitems = new Pair
>(//左右树的根节点 8 new BinaryTree
("root-left"), 9 new BinaryTree
("root-right") 10 ); 11 jfkFamilyTree.Subitems.First.Subitems = new Pair
>(//左子树的左右子树的根节点 12 new BinaryTree
("root-left-left"), 13 new BinaryTree
("root-left-right") 14 ); 15 jfkFamilyTree.Subitems.Second.Subitems = new Pair
>(//右子树的左右子树的根节点 16 new BinaryTree
("root-righ-left"), 17 new BinaryTree
("root-righ-right") 18 ); 19 20 //遍历二叉树 21 foreach (string node in jfkFamilyTree) 22 { 23 Console.WriteLine(node); 24 } 25 26 27 28 Console.ReadLine(); 29 30 31 } 32 33 34 } 35 36 //二叉树(根节点,左右子树) 37 public class BinaryTree
: IEnumerable
38 { 39 40 public BinaryTree(T value) 41 { 42 _value = value; 43 } 44 45 private T _value;//根节点 46 public T Value 47 { 48 get { return _value; } 49 set { _value = value; } 50 } 51 private Pair
> _subitems;//子树,总共两个,左子树和右子数 52 public Pair
> Subitems//子树元素,每一项都是一个树 53 { 54 get { return _subitems; } 55 set 56 { 57 IComparable first; 58 first = (IComparable)value.First.Value; 59 if (first.CompareTo(value.Second.Value) < 0) 60 { 61 //first is less than second. 62 //在这可以做处理,比如交换,或者报错提示 63 //不过值类型赋值不会成功,需要使用其它方式,如换成类而不是struct 64 } 65 else 66 { 67 //first is not less than second. 68 } 69 _subitems = value; 70 } 71 } 72 public IEnumerator
GetEnumerator() 73 { 74 yield return Value;//返回根节点 75 76 foreach (BinaryTree
tree in Subitems)//遍历所有子树,从左向右遍历 77 { 78 if (tree != null) 79 { 80 foreach (T item in tree)//遍历子树当中的所有节点,会引起BinaryTree
中的GetEnumerator()递归调用 81 { 82 yield return item; 83 } 84 85 } 86 } 87 88 89 } 90 #region IEnumerable Members 91 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 92 { 93 return GetEnumerator(); 94 } 95 #endregion 96 97 98 } 99 100 //子树类(左右子树)101 public struct Pair
: IEnumerable
102 {103 private T _first;//左子树104 private T _second;//右子树105 public T First106 {107 get { return _first; }108 set { _first = value; }109 }110 public T Second111 {112 get { return _second; }113 set { _second = value; }114 }115 116 public Pair(T first, T second)117 {118 _first = first;119 _second = second;120 }121 #region IEnumerable
122 public IEnumerator
GetEnumerator()123 {124 yield return First;125 yield return Second;126 }127 #endregion128 129 #region IEnumerable Members130 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()131 {132 return GetEnumerator();133 }134 #endregion135 }

 

 
输出(此输出为优先遍历左子节点):
root
root-left
root-left-left
root-left-right
root-right
root-righ-left
root-righ-right
 
 
7、取消更多的迭代:yield break
 
为此,可以包含一个if语句,不执行代码余下的语句。
然而,也可以跳回调用点,造成MoveNext()返回false
 
yield break语句类似于确定一个函数无事可做的时候,就执行一个return语句。
 
 
迭代器是如何工作的:
C#编译器遇到一个迭代器时,会根据枚举模式,将代码展开成恰当的CIL。
在生成的代码中,C#编译器首先创建一个嵌套的private类来实现IEnumerator<T>接口,
以及它的Current属性和一个MoveNext()方法。
Current属性返回与迭代器的返回类型对应的一个类型。
所以C#的迭代器和手工实现了枚举模式的类具有相同的性能特征。
虽然没有直观的性能提升,但显著提高了开发者的效率。
 
8、在单个类中创建多个迭代器
在之前的迭代器例子中,我们实现了IEnumerable<T>.GetEnumerator()。
这是foreach要隐式寻找的方法。
有的时候,可能希望不同的迭代顺序,比如逆向迭代,对结果进行筛选或是对对象投射进行迭代等。
为了在类中声明额外的迭代器,你可以把它们封装到返回IEnumerable<T>或IEnumerable的属性或方法中。
1             Pair
game = new Pair
("first","second"); 2 3 4 foreach (string name in game.GetReverseEnumerator()) 5 { 6 Console.WriteLine(name); 7 } 8 9 public struct Pair
: IEnumerable
10 {11 ...12 public IEnumerable
GetReverseEnumerator()13 {14 yield return Second;15 yield return First;16 }17 }

 

 
输出:
second
first
 
注意,这里返回的是IEnumerable<T>,而不是IEnumerator<T>。
 
9、yield语句的特征
只有返回IEnumerator<T>或者IEnumerable<T>类型(或者它们的非泛型版本)
的成员中,才能声明yield return语句。更具体地说,只有在返回IEnumerator<T>
的GetEnumerator()方法中,或者在返回IEnumerable<T>但不叫做GetEnumerator()的方法中
,才能声明yield return。
包含yield return语句的方法不能包含一个简单的return语句。
假如方法使用了yield return语句,则C#编译会生成必要的代码为迭代器维持一个状态机。
与此相反,假如方法使用return语句,而不是yield return,就要由程序员负责维护他自己的
状态机,并返回其中一个迭代器接口的实例。
迭代器中的所有代码路径都必须包含一个yield return语句。
 
yiled语句的其他限制包括如下方面:
a、yield语句不能在一个方法、运算符或者属性访问器的外部出现
b、不能在一个匿名方法中出现
c、不能在try语句的catch和finally子句中出现。除此之外,只有在没有
catch块的try块中,才可以出现field语句。(更正确的说法是,如果try块后面就是一个finally块,
就可以在try块中放一个yield return语句
 
自从在C#2.0中引入了泛型集合类和泛型接口之后,应该优先执行泛型而不是非泛型版本。
由于避免了因为装箱而造成的开销,而且能在编译时强制遵守类型规则,所以这些泛型版本更快、更安全。
 
另一个引人注目的新特性是迭代器。
C#利用yield这个关键字来生成底层的CIL代码,从而实现由foreach循环使用的迭代器模式。
 
 

转载于:https://www.cnblogs.com/tlxxm/p/4675018.html

你可能感兴趣的文章
箭头函数
查看>>
npm--依赖管理 2
查看>>
设计模式 开闭原则
查看>>
UI设计小白怎样学才能快速入门?
查看>>
用Redis轻松实现秒杀系统
查看>>
埋在MySQL数据库应用中的17个关键问题!
查看>>
APP瘦身这一篇就够了
查看>>
Spark in action on Kubernetes - 存储篇(一)
查看>>
Java springboot B2B2C o2o多用户商城 springcloud架构:服务消费(Feign)
查看>>
15. C#数据结构与算法 -- 栈
查看>>
2015年8月27日【文件权限管理及grep正则和扩展正则表达式】-JY1506402-19+liuhui880818...
查看>>
[SSIS] 之七: SSIS 学习之旅 FTP访问类
查看>>
让git push命令不再每次都输入密码 ssh配置_已迁移
查看>>
配置Linux主机SSH无密码访问
查看>>
saltstack 结束正在执行的任务
查看>>
orangepi,linux硬盘自动休眠
查看>>
6个变态的C语言Hello World程序
查看>>
徒手撸出一个类Flask微框架(一) 理解HTTP请求 request和response
查看>>
在 CentOS7 上安裝 VMware vSphere CLI (vcli)
查看>>
《java 2 编程21天自学通》
查看>>