常用集合类及构建自定义集合
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. public class BinaryTree<T> : IEnumerable<T>
  2. {
  3.  
  4. public BinaryTree(T value)
  5. { }
  6. public IEnumerator<T> GetEnumerator()
  7. {
  8.  
  9. }
  10. public T Value
  11. {
  12. get { return _value; }
  13. set { _value = value; }
  14. }
  15. private T _value;
  16.  
  17. }
  18. public struct Pair<T>
  19. {
  20. private T _first;
  21. private T _second;
  22. public T First
  23. {
  24. get { return _first; }
  25. set { _first = value; }
  26. }
  27. public T Second
  28. {
  29. get { return _second; }
  30. set { _second = value; }
  31. }
  32. public Pair(T first,T second)
  33. {
  34. _first = first;
  35. _second = second;
  36.  
  37. }
  38. }
 
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. public class CSharpPrimitiveTypes : IEnumerable<string>
  18. {
  19. public IEnumerator<string> GetEnumerator()
  20. {
  21. yield return "object";
  22. yield return "byte";
  23. yield return "uint";
  24. yield return "ulong";
  25. yield return "float";
  26. yield return "char";
  27. yield return "bool";
  28. yield return "string";
  29. }
  30.  
  31. //因为System.Collections.Generic.IEnumerable<T>继承
  32. //System.Collections.IEnumerable这个接口,所以也需要实现
  33. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  34. {
  35. return GetEnumerator();
  36. }
  37. }
 
输出:
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<string> fullname = new Pair<string>("xxm", "yyp");
  6. foreach (string name in fullname)
  7. {
  8. Console.WriteLine(name);
  9. }
  10.  
  11. Console.ReadLine();
  12.  
  13. }
  14.  
  15. }
  16.  
  17. public struct Pair<T> : IEnumerable<T>
  18. {
  19. private T _first;
  20. private T _second;
  21. public T First
  22. {
  23. get { return _first; }
  24. set { _first = value; }
  25. }
  26. public T Second
  27. {
  28. get { return _second; }
  29. set { _second = value; }
  30. }
  31. public Pair(T first, T second)
  32. {
  33. _first = first;
  34. _second = second;
  35. }
  36. #region IEnumerable<T>
  37. public IEnumerator<T> GetEnumerator()
  38. {
  39. yield return First;
  40. yield return Second;
  41. }
  42. #endregion
  43. #region IEnumerable Members
  44. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  45. {
  46. return GetEnumerator();
  47. }
  48. #endregion
  49. }
 
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<string> jfkFamilyTree = new BinaryTree<string>("root");//根节点
  6.  
  7. jfkFamilyTree.Subitems = new Pair<BinaryTree<string>>(//左右树的根节点
  8. new BinaryTree<string>("root-left"),
  9. new BinaryTree<string>("root-right")
  10. );
  11. jfkFamilyTree.Subitems.First.Subitems = new Pair<BinaryTree<string>>(//左子树的左右子树的根节点
  12. new BinaryTree<string>("root-left-left"),
  13. new BinaryTree<string>("root-left-right")
  14. );
  15. jfkFamilyTree.Subitems.Second.Subitems = new Pair<BinaryTree<string>>(//右子树的左右子树的根节点
  16. new BinaryTree<string>("root-righ-left"),
  17. new BinaryTree<string>("root-righ-right")
  18. );
  19.  
  20. //遍历二叉树
  21. foreach (string node in jfkFamilyTree)
  22. {
  23. Console.WriteLine(node);
  24. }
  25.  
  26. Console.ReadLine();
  27.  
  28. }
  29.  
  30. }
  31.  
  32. //二叉树(根节点,左右子树)
  33. public class BinaryTree<T> : IEnumerable<T>
  34. {
  35.  
  36. public BinaryTree(T value)
  37. {
  38. _value = value;
  39. }
  40.  
  41. private T _value;//根节点
  42. public T Value
  43. {
  44. get { return _value; }
  45. set { _value = value; }
  46. }
  47. private Pair<BinaryTree<T>> _subitems;//子树,总共两个,左子树和右子数
  48. public Pair<BinaryTree<T>> Subitems//子树元素,每一项都是一个树
  49. {
  50. get { return _subitems; }
  51. set
  52. {
  53. IComparable first;
  54. first = (IComparable)value.First.Value;
  55. if (first.CompareTo(value.Second.Value) < )
  56. {
  57. //first is less than second.
  58. //在这可以做处理,比如交换,或者报错提示
  59. //不过值类型赋值不会成功,需要使用其它方式,如换成类而不是struct
  60. }
  61. else
  62. {
  63. //first is not less than second.
  64. }
  65. _subitems = value;
  66. }
  67. }
  68. public IEnumerator<T> GetEnumerator()
  69. {
  70. yield return Value;//返回根节点
  71.  
  72. foreach (BinaryTree<T> tree in Subitems)//遍历所有子树,从左向右遍历
  73. {
  74. if (tree != null)
  75. {
  76. foreach (T item in tree)//遍历子树当中的所有节点,会引起BinaryTree<T>中的GetEnumerator()递归调用
  77. {
  78. yield return item;
  79. }
  80.  
  81. }
  82. }
  83.  
  84. }
  85. #region IEnumerable Members
  86. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  87. {
  88. return GetEnumerator();
  89. }
  90. #endregion
  91.  
  92. }
  93.  
  94. //子树类(左右子树)
  95. public struct Pair<T> : IEnumerable<T>
  96. {
  97. private T _first;//左子树
  98. private T _second;//右子树
  99. public T First
  100. {
  101. get { return _first; }
  102. set { _first = value; }
  103. }
  104. public T Second
  105. {
  106. get { return _second; }
  107. set { _second = value; }
  108. }
  109.  
  110. public Pair(T first, T second)
  111. {
  112. _first = first;
  113. _second = second;
  114. }
  115. #region IEnumerable<T>
  116. public IEnumerator<T> GetEnumerator()
  117. {
  118. yield return First;
  119. yield return Second;
  120. }
  121. #endregion
  122.  
  123. #region IEnumerable Members
  124. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  125. {
  126. return GetEnumerator();
  127. }
  128. #endregion
  129. }
 
输出(此输出为优先遍历左子节点):
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<string> game = new Pair<string>("first","second");
  2.  
  3. foreach (string name in game.GetReverseEnumerator())
  4. {
  5. Console.WriteLine(name);
  6. }
  7.  
  8. public struct Pair<T> : IEnumerable<T>
  9. {
  10. ...
  11. public IEnumerable<T> GetReverseEnumerator()
  12. {
  13. yield return Second;
  14. yield return First;
  15. }
  16. }
 
输出:
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循环使用的迭代器模式。
 
 

十六、C# 常用集合类及构建自定义集合(使用迭代器)的更多相关文章

  1. (转)JAVA 十六个常用工具类

    一. org.apache.commons.io.IOUtils closeQuietly 关闭一个IO流.socket.或者selector且不抛出异常.通常放在finally块 toString ...

  2. 《C#本质论》读书笔记(16)构建自定义集合

    16.1 更多集合接口 集合类(这里指IEnumerable层次结构)实现的接口层次结构 16.1.1 IList<T>与IDictionary<TKey,TValue> 字典 ...

  3. Python学习(三十六)—— Cookie、Session和自定义分页

    一.Django中操作Cookie 获取Cookie request.COOKIES['key'] request.get_signed_cookie(key, default=RAISE_ERROR ...

  4. 大数据笔记(十六)——Hive的客户端及自定义函数

    一.Hive的Java客户端 JDBC工具类:JDBCUtils.java package demo.jdbc; import java.sql.DriverManager; import java. ...

  5. MFC编程入门之二十六(常用控件:滚动条控件ScrollBar)

    回顾上一节,讲的是组合框控件Combo Box的使用.本节详解滚动条控件Scroll Bar的相关内容. 滚动条控件简介 滚动条大家也很熟悉了,Windows窗口中很多都有滚动条.前面讲的列表框和组合 ...

  6. VS2010/MFC编程入门之二十六(常用控件:滚动条控件Scroll Bar)

    回顾上一节,鸡啄米讲的是组合框控件Combo Box的使用.本节详解滚动条控件Scroll Bar的相关内容. 滚动条控件简介 滚动条大家也很熟悉了,Windows窗口中很多都有滚动条.前面讲的列表框 ...

  7. 测开之路三十六:常用的css选择器

    在static下新建一个css,并写入内容 /*标签选择器,label标签的颜色为红色*/label {color: red;} /*.代表类选择器,绿色*/.test {color: green;} ...

  8. Gradle 1.12 翻译——第十六章. 使用文件

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  9. JAVA之旅(二十六)——装饰设计模式,继承和装饰的区别,LineNumberReader,自定义LineNumberReader,字节流读取操作,I/O复制图片

    JAVA之旅(二十六)--装饰设计模式,继承和装饰的区别,LineNumberReader,自定义LineNumberReader,字节流读取操作,I/O复制图片 一.装饰设计模式 其实我们自定义re ...

随机推荐

  1. .net framework client profile

    .NET Framework Client Profile The .NET Client Profile is a subset of the .NET Framework, which was p ...

  2. squid透明代理+iptables防火墙,多网卡详细配置

    squid透明代理+iptables防火墙详细配置 看到很多人都在不停地问iptables+squid做透明代理的问题,鄙人不才,斗胆在此做统一解答.如有任何纰漏还望各位批评指教. ========= ...

  3. 大脑提取每一个体素26领域的matlab代码

    %-------------- outer loop for x= 1:40 for y =1:48 for z =1:34 %----------inter loop x=20; y=30; z=1 ...

  4. “ORA-12545: 因目标主机或对象不存在,连接失败”怎么办?

    大概知道是因为主机名或者IP地址的原因引起的,但是不知道究竟,就去百度上查了查,然后就根据几种答案做出了以下就该: 1.E:\oracle\product\10.2.0\db_1\NETWORK\AD ...

  5. 制作手机浏览器显示格式的HTML页面

    最近要推出手机支持访问的HTML页面效果,而这在制作手机页面的过程中状况连连. 主要一下就我制作的工程中所遇的问题说明一下: 1. 改掉HTML页面声明:(以往大部分页面都是HTML4.0的声明) 还 ...

  6. std::min 与std::max 的 Compiler Error C2780

    代码 #include<iostream>#include <algorithm> // std::min#undef minint main(){ float a =15.0 ...

  7. Android项目开发全程(二)--Afinal用法简单介绍

    本篇博文接上篇的<Android项目开发全程(一)--创建工程>,主要介绍一下在本项目中用到的一个很重要的框架-Afinal,由于本系列博文重点是项目开发全程,所以在这里就先介绍一下本项目 ...

  8. [转]让程序在崩溃时体面的退出之SEH

    原文地址:http://blog.csdn.net/starlee/article/details/6636723 SEH的全称是Structured Exception Handling,是Wind ...

  9. ACM_2057

    /* 2013暑期多校联合训练 - 80 +高校,300 +队伍,10000元奖金,敬请期待? A + B再次 时间限制:1000/1000 MS(JAVA /其他)内存限制:32768分之32768 ...

  10. [Java] JavaMail 发送带图片的 html 格式的邮件

    JavaMail 发送的邮件正文和附件是相互独立的,但是内置图片需要定位图片在正文中的位置,所以内置图片和邮件正文是互相依赖的. 发送带附件的邮件可参考JavaMail 发送 html 格式.带附件的 ...