编写高质量代码改善C#程序的157个建议:第17个建议之多数情况下使用foreach进行循环遍历
今天是我看《编写高质量代码:改善C#程序的157个建议》第二遍的时候了,看完这本书的确是受益匪浅,学到了很多东西,也明白了很多道理。
里面的代码我每个都调试了一遍,有时候是有些出入的,可能是作者写的书比较早,使用的开发环境比较旧,也许是我的学习还不到家,今天在看建议17的时候,发现了一些小问题,不是很大,是小问题,记录下来,当别人看到的时候可以起到修正的作用。
可能很多人和我一样,没有太在乎for和foreach的区别,也没有深究其底层发生的一些东西,看了文章才发现里面的东西还真是不少。
好了,我们从头聊一下,我对foreach的认识。
Gof的23种设计模式,我相信大家都看过,我现在也正在写有关《设计模式》的一些文章。在这些设计模式种,有一个设计模式叫“迭代器”,这种设计模式就是针对集合对象的迭代而产生的。具体的模式我就不介绍了,FCL框架中也有针对的此模式的具体实现,具体的接口分别是IEnumerable和IEnumerator接口。迭代器模式屏蔽了各种集合的内部实现,为客户对集合的迭代生成了一个统一接口。foreach的使用语法就是针对FCL框架中实现的迭代器的统一操作实现,简化了语法,方便了客户的实现。
通过该文章和对IL代码的分析,foreach除了可以提供简化语法外,还有另外两个有事:
1、自动将代码置入try-finally块。
2、若类型实现了IDisposable接口,他会在循环结束后自动调用Dispose方法。
这是书里的原话,但是这两大优点是有出入的,并不是所有的集合类型都会将代码自动放入try-finally块中。
我们先来了解一下集合概况吧,集合类型主要分为两种,一种是线性集合,另一种是非线性集合,如图:
1、我们先针对线性集合测试,看看结果怎么样:
1)、线性集合里面的直接存取类型的代表:数组
int[] arra = new int[] { , , , , , , }; foreach (int item in arra)
{
Console.WriteLine(item);
}
代码运行结果是:
我们再来看看他们所生成的IL代码:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 56 (0x38)
.maxstack
.locals init ([] int32[] arra,
[] int32[] V_1,
[] int32 V_2,
[] int32 item)
IL_0000: nop
IL_0001: ldc.i4.
IL_0002: newarr [mscorlib]System.Int32
IL_0007: dup
IL_0008: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=28' '<PrivateImplementationDetails>'::'447E020739FB3351B9350DB35F297A3AD27669A4'
IL_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0012: stloc.
IL_0013: nop
IL_0014: ldloc.
IL_0015: stloc.
IL_0016: ldc.i4.
IL_0017: stloc.
IL_0018: br.s IL_002b
IL_001a: ldloc.
IL_001b: ldloc.
IL_001c: ldelem.i4
IL_001d: stloc.
IL_001e: nop
IL_001f: ldloc.
IL_0020: call void [mscorlib]System.Console::WriteLine(int32)
IL_0025: nop
IL_0026: nop
IL_0027: ldloc.
IL_0028: ldc.i4.
IL_0029: add
IL_002a: stloc.
IL_002b: ldloc.
IL_002c: ldloc.
IL_002d: ldlen
IL_002e: conv.i4
IL_002f: blt.s IL_001a
IL_0031: call int32 [mscorlib]System.Console::Read()
IL_0036: pop
IL_0037: ret
} // end of method Program::Main
我们针对数组执行foreach迭代,但是在所生成的IL代码中并没有看到try-finally中,最起码连try和finally这个两个字样都没看到,书中说foreach遍历集合会自动生成try-finally块,这是不准确的,最起码数组没有生成try-finally代码块。
2)、然后我们再测试一下string类型,看看他的运行结果怎么样!
代码如下:
string dd = "我是中国人";
foreach (var item in dd)
{
Console.WriteLine(item);
}
执行效果如图:
让我们再来看看它所生成的IL代码吧,不要惊讶:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 51 (0x33)
.maxstack
.locals init ([] string dd,
[] string V_1,
[] int32 V_2,
[] char item)
IL_0000: nop
IL_0001: ldstr bytearray ( 2F 2D 4E FD BA 4E ) // .b/f-N.V.N
IL_0006: stloc.
IL_0007: nop
IL_0008: ldloc.
IL_0009: stloc.
IL_000a: ldc.i4.
IL_000b: stloc.
IL_000c: br.s IL_0023
IL_000e: ldloc.
IL_000f: ldloc.
IL_0010: callvirt instance char [mscorlib]System.String::get_Chars(int32)
IL_0015: stloc.
IL_0016: nop
IL_0017: ldloc.
IL_0018: call void [mscorlib]System.Console::WriteLine(char)
IL_001d: nop
IL_001e: nop
IL_001f: ldloc.
IL_0020: ldc.i4.
IL_0021: add
IL_0022: stloc.
IL_0023: ldloc.
IL_0024: ldloc.
IL_0025: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_002a: blt.s IL_000e
IL_002c: call int32 [mscorlib]System.Console::Read()
IL_0031: pop
IL_0032: ret
} // end of method Program::Main
这里面也没有生成try-finally代码块,说明并不是所有的集合都会自动生成try-finally代码块的。
3)、我们继续测试一下Dictionary<TKey,TValue>,List<T>,Queue<T>,Stack<T>,ArrayList类型:
(1)、Dictionary<TKey,TValue>测试代码:
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("", "");
dictionary.Add("", "");
dictionary.Add("", "");
dictionary.Add("", "");
dictionary.Add("", "");
dictionary.Add("", "");
dictionary.Add("", ""); foreach (var item in dictionary)
{
Console.WriteLine(item.Key + "----" + item.Value);
}
字典类型所生成IL代码如下:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 209 (0xd1)
.maxstack
.locals init ([] class [mscorlib]System.Collections.Generic.Dictionary`<string,string> dictionary,
[] valuetype [mscorlib]System.Collections.Generic.Dictionary`/Enumerator<string,string> V_1,
[] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`<string,string> item)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::.ctor()
IL_0006: stloc.
IL_0007: ldloc.
IL_0008: ldstr ""
IL_000d: ldstr ""
IL_0012: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::Add(!,
!)
IL_0017: nop
IL_0018: ldloc.
IL_0019: ldstr ""
IL_001e: ldstr ""
IL_0023: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::Add(!,
!)
IL_0028: nop
IL_0029: ldloc.
IL_002a: ldstr ""
IL_002f: ldstr ""
IL_0034: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::Add(!,
!)
IL_0039: nop
IL_003a: ldloc.
IL_003b: ldstr ""
IL_0040: ldstr ""
IL_0045: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::Add(!,
!)
IL_004a: nop
IL_004b: ldloc.
IL_004c: ldstr ""
IL_0051: ldstr ""
IL_0056: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::Add(!,
!)
IL_005b: nop
IL_005c: ldloc.
IL_005d: ldstr ""
IL_0062: ldstr ""
IL_0067: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::Add(!,
!)
IL_006c: nop
IL_006d: ldloc.
IL_006e: ldstr ""
IL_0073: ldstr ""
IL_0078: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::Add(!,
!)
IL_007d: nop
IL_007e: nop
IL_007f: ldloc.
IL_0080: callvirt instance valuetype [mscorlib]System.Collections.Generic.Dictionary`/Enumerator<!,!> class [mscorlib]System.Collections.Generic.Dictionary`<string,string>::GetEnumerator()
IL_0085: stloc.
.try
{
IL_0086: br.s IL_00b0
IL_0088: ldloca.s V_1
IL_008a: call instance valuetype [mscorlib]System.Collections.Generic.KeyValuePair`<!,!> valuetype [mscorlib]System.Collections.Generic.Dictionary`/Enumerator<string,string>::get_Current()
IL_008f: stloc.
IL_0090: nop
IL_0091: ldloca.s item
IL_0093: call instance ! valuetype [mscorlib]System.Collections.Generic.KeyValuePair`<string,string>::get_Key()
IL_0098: ldstr "----"
IL_009d: ldloca.s item
IL_009f: call instance ! valuetype [mscorlib]System.Collections.Generic.KeyValuePair`<string,string>::get_Value()
IL_00a4: call string [mscorlib]System.String::Concat(string,
string,
string)
IL_00a9: call void [mscorlib]System.Console::WriteLine(string)
IL_00ae: nop
IL_00af: nop
IL_00b0: ldloca.s V_1
IL_00b2: call instance bool valuetype [mscorlib]System.Collections.Generic.Dictionary`/Enumerator<string,string>::MoveNext()
IL_00b7: brtrue.s IL_0088
IL_00b9: leave.s IL_00ca
} // end .try
finally
{
IL_00bb: ldloca.s V_1
IL_00bd: constrained. valuetype [mscorlib]System.Collections.Generic.Dictionary`/Enumerator<string,string>
IL_00c3: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_00c8: nop
IL_00c9: endfinally
} // end handler
IL_00ca: call int32 [mscorlib]System.Console::Read()
IL_00cf: pop
IL_00d0: ret
} // end of method Program::Main
哈哈哈,终于出现了,自动生成try-finally代码块,并且调用了Dispose方法。
(2)List<T>,Queue<T>,Stack<T>,ArrayList测试代码如下:
List<int> list = new List<int>() { , }; foreach (var item in list)
{
Console.WriteLine(item);
}
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 83 (0x53)
.maxstack
.locals init ([] class [mscorlib]System.Collections.Generic.List`<int32> list,
[] valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<int32> V_1,
[] int32 item)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`<int32>::.ctor()
IL_0006: dup
IL_0007: ldc.i4.
IL_0008: callvirt instance void class [mscorlib]System.Collections.Generic.List`<int32>::Add(!)
IL_000d: nop
IL_000e: dup
IL_000f: ldc.i4.
IL_0010: callvirt instance void class [mscorlib]System.Collections.Generic.List`<int32>::Add(!)
IL_0015: nop
IL_0016: stloc.
IL_0017: nop
IL_0018: ldloc.
IL_0019: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<!> class [mscorlib]System.Collections.Generic.List`<int32>::GetEnumerator()
IL_001e: stloc.
.try
{
IL_001f: br.s IL_0032
IL_0021: ldloca.s V_1
IL_0023: call instance ! valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<int32>::get_Current()
IL_0028: stloc.
IL_0029: nop
IL_002a: ldloc.
IL_002b: call void [mscorlib]System.Console::WriteLine(int32)
IL_0030: nop
IL_0031: nop
IL_0032: ldloca.s V_1
IL_0034: call instance bool valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<int32>::MoveNext()
IL_0039: brtrue.s IL_0021
IL_003b: leave.s IL_004c
} // end .try
finally
{
IL_003d: ldloca.s V_1
IL_003f: constrained. valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<int32>
IL_0045: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_004a: nop
IL_004b: endfinally
} // end handler
IL_004c: call int32 [mscorlib]System.Console::Read()
IL_0051: pop
IL_0052: ret
} // end of method Program::Main
其他的代码我就不贴了,都生成了try-finally代码块,如果类型实现了IDisposable接口,也会调用Dispose方法。
2、我们再测试一下非线性集合是否会自动产生相关代码
我们测试一下HashSet<T>代码,看看效果怎么样。
HashSet<string> hash = new HashSet<string>();
hash.Add("");
hash.Add("");
hash.Add("");
hash.Add("");
hash.Add("");
hash.Add(""); foreach (var item in hash)
{
Console.WriteLine(item);
}
运行效果图:
最后我们看看IL代码:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 139 (0x8b)
.maxstack
.locals init ([] class [System.Core]System.Collections.Generic.HashSet`<string> hash,
[] valuetype [System.Core]System.Collections.Generic.HashSet`/Enumerator<string> V_1,
[] string item)
IL_0000: nop
IL_0001: newobj instance void class [System.Core]System.Collections.Generic.HashSet`<string>::.ctor()
IL_0006: stloc.
IL_0007: ldloc.
IL_0008: ldstr ""
IL_000d: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`<string>::Add(!)
IL_0012: pop
IL_0013: ldloc.
IL_0014: ldstr ""
IL_0019: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`<string>::Add(!)
IL_001e: pop
IL_001f: ldloc.
IL_0020: ldstr ""
IL_0025: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`<string>::Add(!)
IL_002a: pop
IL_002b: ldloc.
IL_002c: ldstr ""
IL_0031: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`<string>::Add(!)
IL_0036: pop
IL_0037: ldloc.
IL_0038: ldstr ""
IL_003d: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`<string>::Add(!)
IL_0042: pop
IL_0043: ldloc.
IL_0044: ldstr ""
IL_0049: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`<string>::Add(!)
IL_004e: pop
IL_004f: nop
IL_0050: ldloc.
IL_0051: callvirt instance valuetype [System.Core]System.Collections.Generic.HashSet`/Enumerator<!> class [System.Core]System.Collections.Generic.HashSet`<string>::GetEnumerator()
IL_0056: stloc.
.try
{
IL_0057: br.s IL_006a
IL_0059: ldloca.s V_1
IL_005b: call instance ! valuetype [System.Core]System.Collections.Generic.HashSet`/Enumerator<string>::get_Current()
IL_0060: stloc.
IL_0061: nop
IL_0062: ldloc.
IL_0063: call void [mscorlib]System.Console::WriteLine(string)
IL_0068: nop
IL_0069: nop
IL_006a: ldloca.s V_1
IL_006c: call instance bool valuetype [System.Core]System.Collections.Generic.HashSet`/Enumerator<string>::MoveNext()
IL_0071: brtrue.s IL_0059
IL_0073: leave.s IL_0084
} // end .try
finally
{
IL_0075: ldloca.s V_1
IL_0077: constrained. valuetype [System.Core]System.Collections.Generic.HashSet`/Enumerator<string>
IL_007d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0082: nop
IL_0083: endfinally
} // end handler
IL_0084: call int32 [mscorlib]System.Console::Read()
IL_0089: pop
IL_008a: ret
} // end of method Program::Main
好了,总结一下吧,如果不涉及到修改集合元素,一般情况下使用Foreach比较好,在我测试的代码中,数组和String数据类型不会生成try-finally代码块,里面有一些出入而已,但是这并不影响Foreach的使用。所以我们在具体的编码过程中,尽量多的使用Foreach吧,没关系。
编写高质量代码改善C#程序的157个建议:第17个建议之多数情况下使用foreach进行循环遍历的更多相关文章
- 编写高质量代码改善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 POI 应用系列(1)--生成Excel
POI简介(官网:http://poi.apache.org/) Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office ...
- Java内存数据模型
本篇文章带来的是对Java内存数据模型的介绍,这对于我们深入理解Jvm虚拟机工作的原理和Java内存的划分大有裨益,好了,为了让我们理解的更为深刻,我们将会加入图片辅助的方法去理解. 本篇博文的目录: ...
- Asp.Net Core轻量级Aop解决方案:AspectCore
什么是AspectCore Project ? AspectCore Project 是适用于Asp.Net Core 平台的轻量级 Aop(Aspect-oriented programming) ...
- java原生实现屏幕设备遍历和屏幕采集(捕获)等功能
前言:本章中屏幕捕获使用原生java实现,屏幕图像显示采用javacv1.3的CanvasFrame 一.实现的功能 1.屏幕设备遍历 2.本地屏幕图像采集(也叫屏幕图像捕获) 3.播放本地图像(采用 ...
- 基于redis实现tomcat8及以上版本的tomcat集群的session持久化实现(tomcat-redis-session-manager二次开发)
前言: 本项目是基于jcoleman的tomcat-redis-session-manager二次开发版本 1.修改了小部分实现逻辑 2.去除对juni.jar包的依赖 3.去除无效代码和老版本tom ...
- xssless - 自动化的XSS payload攻击器
XSSLESS 一个用Python编写的自动化XSS 负载(payload)攻击器 用法: 记录请求 并结合Burp proxy 选择你想生成的请求,然后右键选择“保存项目” 使用xssless生成你 ...
- axis调用webservice的简单方法
package com.service; import org.apache.axis.client.Call; import org.apache.axis.client.Service; publ ...
- HTML5浏览器定位navigator.geolocation.getCurrentPosition
<!DOCTYPE html> <html> <body> <p id="demo">点击这个按钮,获得您的坐标:</p> ...
- 版本控制工具svn的安装与简单使用
版本控制工具多用于多人协作开发项目中,这不同于个人开发项目,想把自己代码怎样放置都可以,而且删除了代码很难查找. 版本控制工具类似于个人处理钱的过程,放于自己口袋管理类似于个人开发情形,如果自己钱丢了 ...
- 转 使用HAProxy,PHPRedis,和MySQL支撑10亿请求每周架构细节
[编者按]在公司的发展中,保证服务器的可扩展性对于扩大企业的市场需要具有重要作用,因此,这对架构师提出了一定的要求.Octivi联合创始人兼软件架构师Antoni Orfin将向你介绍一个非常简单的架 ...