前言
嘿,小伙伴们!
如果你最近读过我的另一篇文章《C# 去掉字符串最后一个字符的 4 种方法》,你可能会对 foreach
循环和 for
循环这两者之间的微妙差异产生了一些好奇。
今天,咱们就来聊聊这两位循环界的 "老炮儿" —— foreach
循环和和 for
循环,看看它们到底有何不同!
foreach 循环的内部实现原理
foreach
循环是 C# 提供的用于简化集合遍历的语法,可以说是 C# 为了方便我们这些懒人而发明的,它让咱们不用去操心那些烦人的细节,直接就可以愉快地遍历集合。
它的内部实现依赖于集合对象是否实现了 IEnumerable
或 IEnumerator
接口。
IEnumerable 接口:这个接口就像是个神奇的门把手,轻轻一拧就能打开遍历的大门,里面定义了一个名为
GetEnumerator
的方法,这个方法会返回一个实现了IEnumerator
接口的对象。IEnumerator 接口:这个接口有几个重要的成员:
MoveNext
方法,用于判断是否还有下一个元素,就像按电梯按钮一样,按一下就能到下一层Current
属性,获取当前元素的值Reset
方法,将迭代器重置到初始位置,实际中很少使用,就像我家里的那台老式收音机,虽然还在,但已经很久没用过了
foreach
循环的基本语法就像这样:
foreach (var item in collection)
{
// 访问 item
}
当使用 foreach
循环时,编译器会生成类似于以下的代码:
IEnumerator<T> enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
T item = enumerator.Current;
// 处理 item
}
}
finally
{
IDisposable disposable = enumerator as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
说明:以上这段代码的关键是 GetEnumerator()
方法返回了一个 IEnumerator
实例,这个实例负责跟踪当前的元素并能够移动到下一个元素。
所以 foreach
循环的内部实现可以简单总结如下:
foreach
循环使用迭代器(iterator)来遍历集合,编译器会悄悄生成一个状态机来管理整个过程。- 通过调用
GetEnumerator()
得到的IEnumerator
对象就像是一个守护者,跟踪当前元素并带你到下一个元素。 MoveNext()
是你前进的钥匙,用于移动到下一个元素,而Current
用于访问当前元素,告诉你当前看到的是啥。foreach
循环在处理任何实现了IEnumerable
接口的集合时,轻松自如。foreach
循环自动将代码置入 try-finally 块- 若类型实现了 IDisposable 接口,
foreach
循环会在循环结束后自动调用 Dispose 方法释放
for 循环的内部实现原理
for
循环是一种传统的循环结构,使用上更为灵活,因为它允许你手动控制循环的开始、结束条件和迭代步长
它的内部实现直接依赖于索引访问或直接迭代,通过索引器来访问每个元素。
for
循环的基本语法如下:
for (int i = 0; i < array.Length; i++)
{
// 访问 array[i]
}
说明:以上这段代码里的 i
是由程序员控制的索引变量,用于访问数组或集合中的元素。
所以对于 for
循环的内部实现,我们可以简单概括如下:
for
循环在编译时会被转换为一个简单的计数器循环,直接通过索引访问数组或集合的元素。- 对于数组来说,编译器能够直接计算出元素的地址,因此访问速度非常快。
- 在处理 List 或其他集合时,
for
依然能顺畅使用索引,但要小心边界检查。
两者区别
经过以上的探索,现在咱们来看看这两哥们儿的区别:
语法:
foreach
循环自动管理迭代,你只要负责享受就好。for
循环需要你自己管理索引,要小心别越界!
可读性:
foreach
循环语法优雅简洁,像是一首流畅的诗,读起来让人心情愉悦。for
循环的可读性稍逊一筹,尤其是在多维数组里,就像是迷宫一样。
性能:
foreach
循环对于实现了IEnumerable
的集合可能会稍微慢一点。for
循环对于大数组的性能更好些。
类型安全:
foreach
循环在编译时会检查元素类型,就像是个严格的门卫。for
循环可能会导致运行时类型转换错误,就像是一本悬疑小说,有时惊险莫测。
增删集合:
foreach
不允许你在循环中对集合元素进行增删操作,因为迭代器内部维护了一个对集合版本的控制,任何对集合的增删操作都会使用版本号加1,容易引发异常。for
可以在循环中对集合进行增删操作,因为它直接使用索引器,它不对集合版本号进行判断,所以不存在因为集合的变动而带来的异常(当然,超出索引长度这种情况除外)。
使用场景
foreach 循环的使用场景:
- 程序只需要遍历集合中的每个元素,而不需要增删集合的元素时
- 集合实现了
IEnumerable
接口(如 List、Array、Dictionary 等) - 你希望代码更加简洁和易于理解
使用 for 循环的场景:
- 程序需要通过索引访问元素,比如在多维数组中
- 程序需要在循环中增加或删除集合的元素
- 程序需要在遍历过程中控制循环的步长,比如跳过某些元素
最终建议
- 如果你只需要遍历集合,并且无需增删集合元素,多数情况下
foreach
循环是首选 foreach
循环不能取代for
循环,当你需要访问索引或者需要在循环中增删集合元素时,使用for
循环- 对于程序健壮性要求比较高的程序,尽量使用
foreach
循环,因为它是类型安全的,并且若类型实现了IDisposable
接口,它会在循环结束后自动调用Dispose
方法释放资源 - 对于性能敏感的应用程序,比如处理 10 万条数据以上的大数组,可以考虑使用
for
循环 - 在编写代码时,尽量保持代码的可读性和简洁性,这样可以减少出错的机会
Happy Coding!🎉
往期精彩
我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得。欢迎关注老杨的公众号(名称:代码掌控者),更多干货等你来!