编写高质量代码改善C#程序的157个建议——建议18:foreach不能代替for
建议18:foreach不能代替for
上一个建议中提到了foreach的两个优点:语法更简单,默认调用Dispose方法,所有我们强烈建议在实际的代码编写中更多的使用foreach。但是,该建议也有不适合的场景。
foreach存在一个问题:它不支持循环时对集合进行增删操作。比如,运行下面代码会抛出异常InvalidOperationException:
List<int> list=new List<int>(){,,,};
foreach (int item in list)
{
list.Remove(item);
Console.WriteLine(item);
}
取而代之的方法是使用for循环
for (int i = ; i < list.Count; i++)
{
list.Remove(list[i]);
Console.WriteLine(list[i]);
}
foreach循环使用了迭代器进行集合的遍历,它在FCL提供的跌代替内部维护了一个对集合版本的控制。那么什么是集合版本?简单来说,其实它就是一个整形的变量,任何对集合的增删操作都会使版本号加1.foreach会调用MoveNext方法来遍历元素,在MoveNext方法内部会进行版本号的检测,一旦检测到版本号有变动,就会抛出InvalidOperationException异常。
如果使用for循环就不会带来这样的问题。for直接使用索引器,它不对集合版本号进行判断,所以不会存在以为集合的变动而带来的异常(当然,超出索引长度这种异常情况除外)。
由于for循环和foreach循环实现上有所不同(前者索引器,后者迭代器),因此关于两者性能上的争议从来没有停止过。但是,即使有争议,双方都承认两者在时间和内存上有损耗,尤其是针对泛型集合时,两者的损耗是在同一个数量级别上的。
以类型List<T>为例,索引器如下所示:
[__DynamicallyInvokable]
public T this[int index]
{
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable]
get
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return this._items[index];
}
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable]
set
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
this._items[index] = value;
this._version++;
}
}
迭代器如下所示:
[__DynamicallyInvokable]
public bool MoveNext()
{
List<T> list = this.list;
if ((this.version == list._version) && (this.index < list._size))
{
this.current = list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}
[__DynamicallyInvokable]
public T Current
{
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
get
{
return this.current;
}
}
可以看到,List<T>类内部维护着一个泛型数组:
private T[] _items;
无论是for循环还是foreach循环,内部都是对该数组的访问,而迭代器仅仅是多进行了一次版本检测。事实上,在循环内部,两者生成的IL代码也是差不多的,但是,正如本建议刚开始提到的那样,因为版本检测的缘故,foreach循环并不能代替for循环。
转自:《编写高质量代码改善C#程序的157个建议》陆敏技
编写高质量代码改善C#程序的157个建议——建议18:foreach不能代替for的更多相关文章
- 编写高质量代码改善C#程序的157个建议[1-3]
原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...
- 读书--编写高质量代码 改善C#程序的157个建议
最近读了陆敏技写的一本书<<编写高质量代码 改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...
- 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试
建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...
- 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本
建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...
- 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码
建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...
- 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣
建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...
- 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释
建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...
- 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释
建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...
- 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量
建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...
- 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法
建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...
随机推荐
- Java 编码规范
package(包) 包名的命名规范:1.小写 2.至少有一层目录 3.域名倒置书写 package baidu; package com.baidu.www; Class(类)-----大驼峰法 类 ...
- lnmp php7 安装mysqli扩展 undefined function mysqli_connect()
在用ci框架的时候, https://blog.csdn.net/zqtsx/article/details/8746497 https://blog.csdn.net/move_now/articl ...
- 配置和启动脚本(bash shell学习01)
bash是 Bourne Again Shell简称 ,从unix系统的sh发展而来 查看当前shellecho $SHELL查看系统支持的shellcat /etc/shells cd /binls ...
- linux用rdate命令实现同步时间
用rdate命令实现同步时间 前两天说到用ntp时间服务器和ntpdate命令同步时间,今天简单记录下用rdate同步时间 http://blog.csdn.net/wyzxg/archive/201 ...
- mysql函数之六:mysql插入数据后返回自增ID的方法,last_insert_id(),selectkey
mysql插入数据后返回自增ID的方法 mysql和oracle插入的时候有一个很大的区别是,oracle支持序列做id,mysql本身有一个列可以做自增长字段,mysql在插入一条数据后,如何能获得 ...
- 【转】分布式存储和一致性hash
本文我将对一致性算法作介绍,同时谈谈自己对一致性hash和一般意义上的hash算法的区别 hash是什么 hash即hash算法,又称为散列算法,百度百科的定义是 哈希算法将任意长度的二进制值映射为较 ...
- .gitignore non-valiate
Git忽略规则及.gitignore规则不生效的解决办法 分享到:更多19 2015-02-12 分类:Share2人评论65,215 次人浏览 在git中如果想忽略掉某个文件,不让这个文件提交 ...
- day8-心得
1. Socket介绍 概念 A network socket is an endpoint of a connection across a computer network. Today, mos ...
- K12协同开发在做常见问题时候遇到的问题
一.在做常见问题的时候遇到的问题 在后端处理数据的时候是通过serialize来实现的,从数据库中查出自己想要的数据,直接返回数据. 在前端发送ajax请求获取数据并且在页面上以好看的形式渲染. 1. ...
- Java面向对象-面向对象编程之基本概念
面向对象这个概念,每本书上的说法定义很多. 我自己根据我的经验,自己归档总结了下, 所谓面向对象,就是 以基于对象的思维去分析和解决问题,万物皆对象: 面向对象经常和面向过程放一起讨论: 这里举例, ...