多数情况下使用foreach进行循环遍历

继上一篇“元素数量可变的情况下不应使用数组”后,本文继续看《编写高质量代码改善C#程序的157个建议》一书第17个建议“多数情况下使用foreach进行循环遍历”。喜欢本书请到各大商城购买原书,支持正版。

正文开始:


由于本建议涉及集合的遍历,所以在开始讲解本建议之前,我们不妨来设想一下如何对集合进行遍历。假设存在一个数组,其遍历模式可能采用依据索引来进行遍历的方法﹔又假设存在一个HashTable,其遍历模式可能是按照键值来进行遍历。无论是哪个集合,如果它们的遍历没有一个公共的接口,那么客户端在进行调用的时候,相当于是对具体类型进行了编码。这样一来,当需求发生变化时,就必须修改我们的代码。并且,由于客户端代码过多地关注了集合内部的实现,代码的可移植性就会变得很差,这直接违反了面向对象中的开闭原则。于是,迭代器模式就诞生了。现在,不要管FCL中是如何实现该模式的,我们先来实现一个自己的迭代器模式。

class Program
{
  static void Main(string[] args)
  {
    // 使用接口工MyEnumerable代替MyList
    IMyEnumerable list = new MyList();
    // 得到迭代器,在循环中针对迭代嚣编码,而不是集合MyList
    IMyEnumerator enumerator = list.GetEnumerator();
    for (int i = 0; i < list.Count; i++)
    {
      object current = enumerator.Current;
      enumerator.MoveNext();
    }

    while (enumerator.MoveNext())
    {
      object current = enumerator.Current;
    }
  }
}

/// <summary>
/// 要求所有的迭代器全部实现该接口
/// </summary>
interface IMyEnumerator
{
  bool MoveNext();
  object Current { get; }
}

/// <summary>
/// 要求所有的集合实现该接口
/// 这样一来,客户端就可以针对该接口编码
/// 而无需关注具体的实现
/// </summary>
interface IMyEnumerable
{
  IMyEnumerator GetEnumerator();
  int Count { get; }
}

class MyList : IMyEnumerable
{
  object[] items = new object[10];
  IMyEnumerator myEnumerator;

  public object this[int i]
  {
    get { return items[i]; }
    set { this.items[i] = value; }
  }

  public int Count
  {
    get { return items.Length; }
  }

  public IMyEnumerator GetEnumerator()
  {
    if (myEnumerator == null)
    {
      myEnumerator = new MyEnumerator(this);
    }
    return myEnumerator;

  }
}

class MyEnumerator : IMyEnumerator
{
  int index = 0;
  MyList myList;

  public MyEnumerator(MyList myList)
  {
    this.myList = myList;
  }

  public bool MoveNext()
  {
    if (index + 1 > myList.Count)
    {
      index = 1;
      return false;
    }
    else
    {
      index++;
      return true;
    }
  }

  public object Current
  {
    get { return myList[index - 1]; }
  }
}

MyList模拟了一个集合类,它继承了接口IMyEnumerable,这样,在客户端进行调用的时候,我们就可以直接使用IMyEnumerable来声明变量,如代码中的以下语句:

IMyEnumerable list = new MyList();

如果未来我们新增了其他的集合类,那么针对list的编码即使不做修改也能运行良好。在 IMyEnumerable中声明的GetEnumerator方法返回一个继承了IMyEnumerator的对象。在 MyList的内部,默认返回MyEnumerator。MyEnumerator就是迭代器的一个实现,如果对于迭代的需求有变化,可以重新开发一个迭代器(如下所示),然后在客户端迭代的时候使用该迭代器。

MyEnumerator enumerator2 = new MyEnumerator(list);
while (enumerator2.MoveNext())
{
  object current = enumerator2.Current ;
}

注意,在客户端的代码中,我们在迭代的过程分别演示了for循环和while循环﹔但是因为使用了迭代器的缘故,两个循环都没有针对MyList编码,而是实现了对迭代器的编码。

理解了自己实现的迭代器模式,就相当于理解了FCL中提供的对应模式。以上代码中,在接口和类型名称中都加入了“My”字样,其实,FCL中有与之相对应的接口和类型,只不过为了演示的需要,增删了其中的部分内容,但是大致思路是一样的。使用FCL中相应的类型进行客户端的代码编写,大致应该像下面这样:

ICollection<object> list = new List<object>();
IEnumerator enumerator = list.GetEnumerator();
for (int i = o; i < list.Count; i++)
{
  object current = enumerator.Current;
  enumerator.MoveNext();
}
while (enumerator.MoveNext())
{
  object current = enumerator.Current;
}

但是,无论是 for循环还是while循环,都有些啰嗦,于是,foreach就出现了。在上面的代码中,如果循环用foreach 代替会是什么样子?代码如下所示:

foreach (var current in list)
{
  //省略了object current = enumerator.Current ;
}

可以看到,采用foreach最大限度地简化了代码。它用于遍历一个继承了IEmuerable或IEmuerable<T>接口的集合元素。借助于L代码,我们来查看使用foreach到底发生了什么事情:

多数情况下使用foreach进行循环遍历

在IL代码的开始处,可以看到声明了–个 Enumerator的变量:CS0000。在整个代码执行过程中,虽然C#代码中看不到针对集合对象的方法调用,但是研究IL代码就可以看出,运行时还是会调用集合类型的get_Current和 MoveNext方法:

多数情况下使用foreach进行循环遍历

在调用完MoveNext方法后,如果结果是true,跳转到循环开始处。实际上,foreach循环和如下的while循环是一样的:

while (enumerator.MoveNext())
{
  object current = enumerator.Current;
}

继续分析L代码我们还可以知道,foreach循环除了可以提供简化的语法外,还有另外两个优势:

  • 自动将代码置入try-finally块。
  • 若类型实现了IDispose接口,它会在循环结束后自动调用Dispose方法。

如果用Reflector进行反编译,则对应的C#代码如下所示:

List<object> list = new List<object>();
using (List<object>.Enumerator CS$5$0000 = list.GetEnumerator ())
{
  while (CS$5$0000.MoveNext())
  {
    object current = CS$5$0000.Current;
  }
}

注意 using 是 try-finally 的语法糖。上面的代码等同于:

List<object>.Enumerator CS$5$0000 = list.GetEnumerator();
try
{
  // 循环
}
finally
{
  CS$5$0000.Dispose();
}

下一篇我们接着读”建议18:foreach不能代替for”,欢迎关注微信公众号【乐趣课堂】。

原文出处:微信公众号【乐趣课堂】

原文链接:https://mp.weixin.qq.com/s/te4RNOBWAzEU_c7dX90MiQ

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论