foreach和for要尽可能使用foreach
前言
由于涉及到集合的遍历,所以在开始之前,不妨想一下如何对集合进行遍历,假设存在一个数组,其遍历模式可能采用索引来进行遍历,假设存在一个HashTable 其遍历模式可能是按照键值对的方式进行的,无论是哪个集合,他们的遍历没有一个公共的接口,那么在客户端进行调用的时候,就必须修改代码,并且由于客户端代码过多的关注了集合的内部实现,代码的可以执行就会变得很差,这直接违反了面向对象编程的开闭原则,于是迭代器的模式就诞生了,现在我们实现自己的一个迭代器。
代码示例
IMyEnumerator
/// /// 要求所有迭代器实现该接口 /// interface IMyEnumerator { bool MoveNext(); object Current { get; } }
IMyEnumerable
/// /// 要求所有集合实现该接口 /// 这样一来客户端就可以针 /// 对接口编码而无需关注具 /// 体实现 /// interface IMyEnumerable { IMyEnumerator GetEnumerator(); int Count { get; } }
MyList
class MyList : IMyEnumerable { object[] items = new object[10]; IMyEnumerator myEnumerator; public object this[int i] { get => 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; } }
MyEnumerator
internal class MyEnumerator : IMyEnumerator { MyList myList; int index = 0; 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]} } }
调用
IMyEnumerable list = new 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; }
使用FCL中相应的类型进行客户端代码的编写如下
ICollection list1 = new List(); IEnumerator enumerator1 = list1.GetEnumerator(); for (int i = 0; i < list1.Count; i++) { object current = enumerator1.Current; enumerator1.MoveNext(); } while (enumerator1.MoveNext()) { object current = enumerator1.Current; }
无论是for还是while循环显得都有些啰嗦于是foreach出现了,foreach除了简化语法以外还有两个优势
1:自动将代码放入 try-finally块
2:若类型实现了IDespose接口,他会在循环结束的时候自动调用Dispose方法
foreach (var item in list1) { //可以直接使用item }
但是foreach并不能替代for,foreach一个问题,它不支持循环的时候进行集合的增删操作,比如运行下面的代码将会报异常
具体说明:
foreach循环使用了迭代器进行集合的遍历,它在FCL迭代器内部维护了对集合版本的控制,简单来说集合版本其实就是一个整型的变量,对任何集合的增删操作都会使本版号+1,foreach会调用MoveNext方法来遍历元素,此方法会进行版本号的检测,一旦检测到有变动,就会抛出InvalidOperationException异常 For循环直接利用索引器,他不对集合进行版本号判断,所以不存在因为集合版本号变动带来的异常(超出索引的除外)
List<int> list3 = new List<int> { 0, 1, 2, 3 }; foreach (var item in list3) { list3.Remove(item);//报错 Console.WriteLine(item.ToString()); } //取而代之的是for for (int i = 0; i < list3.Count; i++) { list3.Remove(list3[i]); Console.WriteLine(list3[i].ToString()); }
写在最后
foreach在编程中起到至关重要的决定,了解它的原理能帮助我们更好的写出高质量的代码,希望此文章能对大家有所帮助,文章每天更新,感谢关注!长按可查看Gitee源码,代码和教程同步更新!