数组、集合的接口和枚举介绍

39 篇文章 1 订阅

5.5  数组和集合接口

Array类实现了IEumerableICollectionIList接口,以访问和枚举数组中的元素。由于用定制数组创建的类派生于Array抽象类,所以能使用通过数组变量执行的接口中的方法和属性。

5.5.1  IEumerable接口

IEumerable是由foreach语句用于迭代数组的接口。这是一个非常特殊的特性,在下一节中讨论。

5.5.2  ICollection接口

ICollection接口派生于IEumerable接口,并添加了如表5-2所示的属性和方法。这个接口主要用于确定集合中的元素个数,或用于同步。

  5-2

ICollection接口的属性和方法

   

Count

Count属性可确定集合中的元素个数,它返回的值与Length属性相同

IsSynchronized

SyncRoot

IsSynchronized属性确定集合是否是线程安全的。对于数组,这个属性总是返回false。对于同步访问,SyncRoot属性可以用于线程安全的访问。第19章介绍了线程和同步,探讨了如何用集合实现线程安全性

CopyTo()

利用CopyTo()方法可以将数组的元素复制到现有的数组中。它类似于静态方法Array.Copy()

5.5.3  IList接口

IList接口派生于ICollection接口,并添加了下面的属性和方法。Array类实现IList接口的主要原因是,IList接口定义了Item属性,以使用索引器访问元素。IList接口的许多其他成员是通过Array类抛出NotSupportedException异常实现的,因为这些不应用于数组。IList接口的所有属性和方法如表5-3所示。

  5-3

IList 接 口

   

Add()

Add()方法用于在集合中添加元素。对于数组,该方法会抛出NotSupportedException异常

Clear()

Clear()方法可清除数组中的所有元素。值类型设置为0,引用类型设置为null

Contains()

Contains()方法可以确定某个元素是否在数组中。其返回值是truefalse。这个方法会对数组中的所有元素进行线性搜索,直到找到所需元素为止

IndexOf()

IndexOf()方法与Contains()方法类似,也是对数组中的所有元素进行线性搜索。不同的是,IndexOf()方法会返回所找到的第一个元素的索引

Insert()

Remove()

RemoveAt()

对于集合,Insert()方法用于插入元素,Remove()RemoveAt()可删除元素。对于数组,这些方法都抛出NotSupportedException异常

IsFixedSize

数组的大小总是固定的,所以这个属性总是返回true

IsReadOnly

数组总是可以读/写的,所以这个属性返回false。第10章将介绍如何从数组中创建只读属性

Item

Item属性可以用整型索引访问数组

5.6  枚举

foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数。图5-7显示了调用foreach方法的客户机和集合之间的关系。数组或集合执行带GetEumerator()方法的IEumerable接口。GetEumerator()方法返回一个执行IEumerable接口的枚举。接着,foreach语句就可以使用IEumerable接口迭代集合了。

 

  5-7

提示:

GetEnumerator()方法用IEnumerable接口定义。foreach语句并不真的需要在集合类中执行这个接口。有一个名为GetEnumerator()的方法,返回实现了IEnumerator接口的对象就足够了。

5.6.1  IEnumerator接口

foreach语句使用IEnumerator接口的方法和属性,迭代集合中的所有元素。这个接口中的属性和方法如表5-4所示。

  5-4

IEnumerator接口的方法和属性

   

MoveNext()

MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回false

Current

属性Current返回光标所在的元素

Reset()

Reset()方法将光标重新定位于集合的开头。许多枚举会抛出NotSupportedException异常

5.6.2  foreach语句

C#foreach语句不会解析为IL代码中的foreach语句。C#编译器会把foreach语句转换为IEnumerable接口的方法和属性。下面是一个简单的foreach语句,它迭代persons数组中的所有元素,并逐个显示它们:

foreach (Person p in persons)
{
Console.WriteLine(p);
}

foreach语句会解析为下面的代码段。首先,调用GetEnumerator()方法,获得数组的一个枚举。在while循环中-- 只要MoveNext()返回true-- Current属性访问数组中的元素:

IEnumerator enumerator = persons. GetEnumerator();
while (enumerator.MoveNext())
{
Person p = (Person) enumerator.Current;
Console.WriteLine(p);
}

5.6.3  yield语句

C# 1.0使用foreach语句可以轻松地迭代集合。在C# 1.0中,创建枚举器仍需要做大量的工作。C# 2.0添加了yield语句,以便于创建枚举器。

yield return语句返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代。

下面的例子是用yield return语句实现一个简单集合的代码。类HelloCollection包含GetEnumerator()方法。该方法的实现代码包含两个yield return语句,它们分别返回字符串HelloWorld

using System;
using System.Collection;

namespace Wrox.ProCSharp.Arrays
{
public class HelloCollection
{
public IEumerator GetEumerator()
{
yield return "Hello";
yield return "World";
}
}
}

警告:

包含yield语句的方法或属性也称为迭代块。迭代块必须声明为返回IEnumeratorIEnumerable接口。这个块可以包含多个yield return语句或yield break语句,但不能包含return语句。

现在可以用foreach语句迭代集合了:

public class Program
{
HelloCollection helloCollection = new HelloCollection();
foreach (string s in helloCollection)
{
Console.WriteLine(s);
}
}
}

使用迭代块,编译器会生成一个yield 类型,其中包含一个状态机,如下面的代码所示。yield 类型执行IEnumeratorIDisposable接口的属性和方法。在下面的例子中,可以把yield 类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新的yield 类型。在yield 类型中,变量state定义了迭代的当前位置,每次调用MoveNext()时,当前位置都会改变。MoveNext()封装了迭代块的代码,设置了current变量的值,使Current属性根据位置返回一个对象。

public class HelloCollection
{
public IEnumerator GetEnumerator()
{
Enumerator enumerator = new Enumerator();
return enumerator;
}

public class Enumerator : IEnumerator, IDisposable
{
private int state;
private object 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.Collections.IEnumerator.Reset()
{
throw new NotSupportedException();
}

object System.Collections.IEnumerator.Current
{
get
{
return current;
}
}
void IDisposable.Dispose()
{
}
}
}

现在使用yield return语句,很容易实现允许以不同方式迭代集合的类。类MusicTitles可以用
默认方式通过GetEnumerator()方法迭代标题,用Reverse()方法逆序迭代标题,用Subset()方法搜索子集:

public class MusicTitles
{
string[] names = {
"Tubular Bells", "Hergest Ridge",
"Ommadawn", "Platinum");

  public IEnumerator GetEnumerator()
{
for (int i = 0; i < 4; i++)
{
yield return names[i];
}
}
public IEnumerable Reverse()
{
for (int i = 3; i >= 0; i-)
{
yield return names[i];
}
}
public IEnumerable Subset( int index, int length)
{
for (int i = index; i < index + length; i++)
{
yield return names[i];
}
}
}

迭代字符串数组的客户代码先使用GetEnumerator()方法,该方法不必在代码中编写,因为这是默认使用的方法。然后逆序迭代标题,最后将索引和要迭代的元素个数传送给Subset()方法,来迭代子集:

MusicTitles titles = new MusicTitles();
foreach(string title in titles)
{
ConsoleWriteLine(title);
}
ConsoleWriteLine();

ConsoleWriteLine("reverse");
foreach(string title in titles.Reverse())
{
ConsoleWriteLine(title);
}
ConsoleWriteLine();

ConsoleWriteLine("subset");
foreach(string title in titles.Subset(2, 2))
{
ConsoleWriteLine(title);
}

使用yield语句还可以完成更复杂的任务,例如从yield return中返回枚举器。

TicTacToe游戏中有9个域,玩家轮流在这些域中放置""字或一个圆。这些移动操作由GameMoves类模拟。方法Cross()Circle()是创建迭代类型的迭代块。变量crosscircleGameMoves类的构造函数中设置为Cross()Circle()方法。这些域不设置为调用的方法,而是设置为用迭代块定义的迭代类型。在Cross()迭代块中,将移动操作的信息写到控制台上,并递增移动次数。如果移动次数大于9,就用yield break停止迭代;否则,就在每次迭代中返回yield类型cross的枚举对象。Circle()迭代块非常类似于Cross()迭代块,只是它在每次迭代中返回circle迭代类型。

public calss GameMoves
{
private IEnumerator cross;
private IEnumerator circle;

  public GameMoves()
{
cross = Cross();
circle = Circle();
}

  private int move = 0;

  public IEnumerator Cross()
{
while (true)
{
Console.WriteLine("Cross, move {0}", move);
move++;
if (move > 9)
yield break;
yield return circle;
}
}

public IEnumerator Circle()
{
while (true)
{
Console.WriteLine("Circle, move {0}", move);
move++;
if (move > 9)
yield break;
yield return cross;
}

}

在客户程序中,可以以如下方式使用GameMoves类。将枚举器设置为由game.Cross()返回的枚举器类型,以设置第一次移动。enumerator.MoveNext调用以迭代块定义的一次迭代,返回另一个枚举器。返回的值可以用Current属性访问,并设置为enumerator变量,用于下一次循环:

GameMoves game = new GameMoves();
IEnumerator enumerator = game.Cross();
while (enumerator.MoveNext())
{
enumerator = (IEnumerator) enumerator.Current;
}

这个程序的输出会显示交替移动的情况,直到最后一次移动:

Cross, move 0
Circle, move 1
Cross, move 2
Circle, move 3
Cross, move 4
Circle, move 5
Cross, move 6
Circle, move 7
Cross, move 8

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值