浅谈C#数组(二)
六.枚举集合
在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中元素的个数。foreach语句使用一个枚举器。foreach会调用实现了IEnumerable接口的集合类中的GetEumerator()方法。GetEumerator()方法返回一个实现IEnumerator接口的对象枚举。foreach语句就可以使用IEnumerable接口迭代集合了。
GetEumerator()方法在IEnumerable接口中定义。
1.IEnumerator接口
foreach语句使用IEnumerator接口的方法和属性,迭代集合中所有元素。IEnumerator接口定义了Current属性,来返回光标所在的元素,该接口的MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回false.
这个接口的泛型版本IEnumerator<T>派生自接口IDisposable,因此定义了Dispose()方法,来清理枚举器占用的资源。
2.foreach语句
C#中foreach语句不会解析为IL代码中的foreach语句。C#编译器会把foreach语句转换为IEnumerator接口的方法和属性。
Person[] persons = {
new Person { FirstName="Damon", LastName="Hill" },
new Person { FirstName="Niki", LastName="Lauda" },
new Person { FirstName="Ayrton", LastName="Senna" },
new Person { FirstName="Graham", LastName="Hill" }
};
foreach (Person p in persons)
{
Console.WriteLine(p);
}
foreach语句会解析为下面的代码:
IEnumerator<Person> enumerator = persons.GetEumerator();
while(enumerator.MoveNext())
{
Person p = enumerator.Current;
Console.WriteLine(p);
}
3.yield语句
在C#2.0之前,foreach语句可以轻松的迭代集合,但创建枚举器需要做大量的工作。C#2.0添加了yield语句,以便创建枚举器。
yield return 语句返回集合的一个元素,并移动到下一个元素。yield break可停止迭代。
下面的例子实现返回两个字符串:
public class HelloCollection
{
public IEnumerator<string> GetEnumerator()
{
yield return "Hello";
yield return "World";
}
}
客户端代码:
var helloCollection = new HelloCollection();
foreach (string s in helloCollection)
{
Console.WriteLine(s);
}
包含yield语句的方法或属性也称为迭代块。迭代块必须声明为返回IEnumerator或IEnumerable接口,或者这些接口的泛型版本。这个块可以包含多条yield return语句或yield break语句,但不能包含return语句。
使用迭代块,编译器会生成一个yield类型,其中包含一个状态机,如下面代码所示:
yield类型实现IEnumerator和IDisposable接口的方法和属性。下面的例子可以把yield类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新的yield类型。在yield类型中,变量state定义了迭代的当前位置,每次调用MoveNext()时,当前位置都会改变。MoveNext()封装了迭代块的代码,并设置了current变量的值,从而使Current属性根据位置返回一个对象。
public class HelloCollection
{
public IEnumerator<string> GetEnumerator()
{
return new Enumerator(0);
}
public class Enumerator:IEnumerator<string>,IEnumerator,IDisposable
{
private int state;
private string current;
public Enumerator(int state)
{
this.state = state;
}
bool System.Collections.IEnumerator.MoveNext()
{
switch(state)
{
case 0:
current="hello";
state =1;
return true;
case 1:
current="world";
state =2;
return true;
case 2:
break;
}
return false;
}
void System.Collection>IEnumerator.Reset()
{
throw new NotSupportedException();
}
string System.Collections.Generic.IEnumerator<string>.Current
{
get
{
return current;
}
}
object System.Collections.IEnumerator.Current
{
get
{
return current;
}
}
void IDisposable.Dispose()
{}
}
}
yield语句会产生一个枚举器,而不仅仅生成一个包含的项的列表。这个枚举器通过foreach语句调用。从foreach中依次访问每一项,就会访问枚举器。这样就可以迭代大量的数据,而无需一次把所有的数据都读入内存。
(1).迭代集合的不同方式
可以使用yield return语句,以不同方式迭代集合。
类MusicTitles可以用默认方式通过GetEnumerator()方法迭代标题,该方法不必在代码中编写,也可以用Reverse()逆序迭代标题,用Subset()方法迭代子集合:
public class MusicTitles
{
string[] names = {
"Tubular Bells", "Hergest Ridge",
"Ommadawn", "Platinum" };
public IEnumerator<string> GetEnumerator()
{
for (int i = 0; i < 4; i++)
{
yield return names[i];
}
}
public IEnumerable<string> Reverse()
{
for (int i = 3; i >= 0; i--)
{
yield return names[i];
}
}
public IEnumerable<string> Subset(int index, int length)
{
for (int i = index; i < index + length;i++)
{
yield return names[i];
}
}
}
客户端代码:
var titles = new MusicTitles();
foreach (var title in titles)
{
Console.WriteLine(title);
}
Console.WriteLine();
Console.WriteLine("reverse");
foreach (var title in titles.Reverse())
{
Console.WriteLine(title);
}
Console.WriteLine();
Console.WriteLine("subset");
foreach (var title in titles.Subset(2, 2))
{
Console.WriteLine(title);
}
(2).用yield return 返回枚举器
public class GameMoves
{
private IEnumerator cross;
private IEnumerator circle; public GameMoves()
{
cross = Cross();
circle = Circle();
} private int move = ;
const int MaxMoves = ; public IEnumerator Cross()
{
while (true)
{
Console.WriteLine("Cross, move {0}", move);
if (++move >= MaxMoves)
yield break;
yield return circle;
}
} public IEnumerator Circle()
{
while (true)
{
Console.WriteLine("Circle, move {0}", move);
if (++move >= MaxMoves)
yield break;
yield return cross;
}
}
}
客户端代码:
var game = new GameMoves();
IEnumerator enumerator = game.Cross();
while (enumerator.MoveNext())
{
enumerator = enumerator.Current as IEnumerator;
}
这样会交替调用Cross()和Circle()方法。
七.元组(Tuple)
元组可以合并不同类型的对象。元组起源于函数编程语言,如F#。在.NET Framework中,元组可用于所有的.Net语言。
.NET Framework定义了8个泛型Tuple类和一个静态Tuple类,它们用作元组的工厂。不同的泛型Tuple类支持不同数量的元素。如,Tuple<T1>包含一个元素,Tuple<T1,T2>包含两个元素。
Tuple<string, string> name = new Tuple<string, string>("Jochen", "Rindt");
元组也可以用静态Tuple类的静态Create()方法创建。Create()方法的泛型参数定了要实例化的元组类型:
public static Tuple<int, int> Divide(int dividend, int divisor)
{
int result = dividend / divisor;
int reminder = dividend % divisor;
return Tuple.Create<int, int>(result, reminder);
}
可以用属性Item1和Item2访问元组的项:
var result = Divide(5, 2);
Console.WriteLine("result of division: {0}, reminder: {1}", result.Item1, result.Item2);
如果元组包含的项超过8个,就可以使用带8个参数的Tuple类定义。最后一个模板参数是TRest,表示必须给它传递一个元组。这样,就可以创建带任意个参数的元组了。
var tuple = Tuple.Create<string, string, string, int, int, int, double, Tuple<int, int>>(
"Stephanie", "Alina", "Nagel", 2009, 6, 2, 1.37, Tuple.Create<int, int>(52, 3490));
八.结构比较
数组和元组都实现接口IStructuralEquatable和IStructuralComparable。这两个接口不仅可以比较引用,还可以比较内容。这些接口都是显式实现的,所以在使用时需要把数组和元组强制转换为这个接口。
IStructuralEquatable接口用于比较两个元组或数组是否有相同的内同,IStructuralComparable接口用于给元组或数组排序。
IStructuralEquatable接口示例:
编写实现IEquatable接口的Person类,IEquatable接口定义了一个强类型化的Equals()方法,比较FirstName和LastName的值:
public class Person : IEquatable<Person>
{
public int Id { get; private set; }
public string FirstName { get; set; }
public string LastName { get; set; } public override string ToString()
{
return String.Format("{0}, {1} {2}", Id, FirstName, LastName);
} public override bool Equals(object obj)
{
if (obj == null)
return base.Equals(obj);
return Equals(obj as Person);
} public override int GetHashCode()
{
return Id.GetHashCode();
} #region IEquatable<Person> Members public bool Equals(Person other)
{
if (other == null)
return base.Equals(other); return this.FirstName == other.FirstName && this.LastName == other.LastName;
} #endregion
}
创建两个包含相同内容的Person类型的数组:
var janet = new Person { FirstName = "Janet", LastName = "Jackson" };
Person[] persons1 = { new Person { FirstName = "Michael", LastName = "Jackson" }, janet };
Person[] persons2 = { new Person { FirstName = "Michael", LastName = "Jackson" }, janet };
由于两个变量引用两个不同数组,所以!=返回True:
if (persons1 != persons2)
Console.WriteLine("not the same reference");
对于IStructuralEquatable接口定义的Equals方法,第一个参数是object类型,第二个参数是IEqualityComparer类型。调用这个方法时,通过传递一个实现了EqualityComparer<T>的对象,就可以定义如何进行比较。通过EqualityComparer<T>类完成IEqualityComparer的一个默认实现。这个实现检查T类型是否实现了IEquatable接口,并调用IEquatable.Equals()方法。如果该类没有实现IEquatable接口,就调用Object基类中Equals()方法:
if ((persons1 as IStructuralEquatable).Equals(persons2, EqualityComparer<Person>.Default))
{
Console.WriteLine("the same content");
}
元组示例:
Tuple<>类提供了两个Epuals()方法:一个重写了Object基类中的Epuals方法,并把object作为参数,第二个由IStructuralEquatable接口定义,并把object和IEqualityComparer作为参数。
var t1 = Tuple.Create<int, string>(1, "Stephanie");
var t2 = Tuple.Create<int, string>(1, "Stephanie");
if (t1 != t2)
Console.WriteLine("not the same reference to the tuple");
这个方法使用EqualityComparer<object>.Default获取一个ObjectEqualityComparer<object>,以进行比较。这样就会调用Object.Equals()方法比较元组的每一项:
if (t1.Equals(t2))
Console.WriteLine("equals returns true");
还可以使用TupleComparer类创建一个自定义的IEqualityComparer
TupleComparer tc = new TupleComparer();
if ((t1 as IStructuralEquatable).Equals(t2, tc))
{
Console.WriteLine("yes, using TubpleComparer");
}
class TupleComparer : IEqualityComparer
{
#region IEqualityComparer Members
public new bool Equals(object x, object y)
{
bool result = x.Equals(y);
return result;
}
public int GetHashCode(object obj)
{
return obj.GetHashCode();
}
#endregion
}
浅谈C#数组(二)的更多相关文章
- 浅谈Kotlin(二):基本类型、基本语法、代码风格
浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 浅谈Kotlin(三):类 浅谈Kotlin(四):控制流 通过上面的文章,在A ...
- 浅谈Java代理二:Cglib动态代理-MethodInterceptor
浅谈Java代理二:Cglib动态代理-MethodInterceptor CGLib动态代理特点: 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生 ...
- 浅谈C#数组(一)
如果需要使用同一类型的多个对象,可以使用数组和集合(后面介绍).C#用特殊的记号声明,初始化和使用数组.Array类在后台发挥作用,它为数组中的元素排序和过滤提供了多个方法.使用枚举器,可以迭代数组中 ...
- 浅谈后缀数组SA
这篇博客不打算讲多么详细,网上关于后缀数组的blog比我讲的好多了,这一篇博客我是为自己加深印象写的. 给你们分享了那么多,容我自私一回吧~ 参考资料:这位dalao的blog 一.关于求Suffix ...
- 浅谈js数组中的length属性
前言 一位正在学习前端的菜鸟,虽菜,但还未放弃. 内容 首先,我们都知道每个数组都有一个length属性 这个length属性一般我们用来循环遍历的约束,一般我们都会把他认为是该数组里面有几个元素这个 ...
- 浅谈ES6数组及对象的解构
一.数组的解构,ES6的新特性,主要是方便操作数组,节省不必要的代码,提高代码质量. 上图例子中, example1: 之前想要获取数组中的值,只能挨个获取下标,然后取值 example2:ES6新特 ...
- 浅谈Javascript 数组与字典
Javascript 的数组Array,既是一个数组,也是一个字典(Dictionary). 先举例看看数组的用法. var a = new Array(); a[0] = "Acer&q ...
- 浅谈Spring(二)
一.AOP编程(面向切面编程) AOP的本质是代理. 1.静态代理设计模式 概念:通过代理类为原始类增加额外功能. 代理类 = 原始类 + 额外功能 +实现原始类的相同接口. 优点:避免原始类因为额外 ...
- 浅谈HDFS(二)之NameNode与SecondaryNameNode
NN与2NN工作机制 思考:NameNode中的元数据是存储在哪里的? 假设存储在NameNode节点的硬盘中,因为经常需要随机访问和响应客户请求,必然效率太低,所以是存储在内存中的 但是,如果存储在 ...
随机推荐
- jQuery的基本操作
jQuery就是一个js的库· 主要分为两部分: 1·寻找元素 (选择器,筛选器) 2·操作元素 (CSS的操作,属性的操 ...
- 1671: [Usaco2005 Dec]Knights of Ni 骑士
1671: [Usaco2005 Dec]Knights of Ni 骑士 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 254 Solved: 163 ...
- 从Hash Killer I、II、III论字符串哈希
首先,Hash Killer I.II.III是BZOJ上面三道很经典的字符串哈希破解题.当时关于II,本人还琢磨了好久,但一直不明白为啥别人AC的代码都才0.3kb左右,直到CYG神犇说可以直接随机 ...
- xml与json的原理,区别,优缺点.
1.定义介绍 (1).XML定义扩展标记语言 (Extensible Markup Language, XML) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记数据.定义数据类型,是一种允许 ...
- 求助,如何干掉这个不要脸的“流氓”
问题 chrome 第一次打开时,被一个加"7654 导航"的网站捆绑. 查看设置中启动页中,被设置如下: 解决 域名查看,阿里竟然为这样的网站搞隐私保护 尝试 安 ...
- SQL server 数据库(视图、事物、分离附加、备份还原))
ql Server系列:视图.事物.备份还原.分离附加 视图是数据库中的一种虚拟表,与真实的表一样,视图包含一系列带有名称的行和列数据.行和列数据用来自定义视图的查询所引用的表,并且在引用视图时动态 ...
- android 关于表格布局的认识
表格布局(TableLayout) 使用的知识点有: 控件 TableRow:为这个表格添加一行 table的特殊属性 android:layout_column:确定此表格的列数 android:s ...
- 用shell实现linux系统应用文件清理工具
用shell实现linux系统文件清理工具 1:原始需求 在系统运维中,会产生大量应用备份文件.落地文件等,这些文件需要定时清理.一般来说,都是使用crontab 拉起一个脚本来清理.类似这样: 30 ...
- QT Creator 快速入门教程 读书笔记(二)
一 窗口部件 基础窗口部件QWidget类是所有用户界面对象的基类,窗口和控件都是直接或间接继承自 QWidget,下面我们来看一个很简单的例子: 窗口部件(Widget)简称部件,是QT中建立界面的 ...
- webpack学习笔记(二)-- 初学者常见问题及解决方法
这篇文章是webpack学习第二篇,主要罗列了本人在实际操作中遇到的一些问题及其解决方法,仅供参考,欢迎提出不同意见. 注:本文假设读者已有webpack方面相关知识,故文中涉及到的专有名词不做另外解 ...