编写高质量代码改善C#程序的157个建议——建议61:避免在finally内撰写无效代码
建议61:避免在finally内撰写无效代码
在阐述建议之前,需要先提出一个问题:是否存在一种打破try-finally执行顺序的情况,答案是:不存在(除非应用程序本身因为某些很少出现的特殊情况在try块中退出)。应该始终认为finally内的代码会在方法return之前执行,哪怕return在try块中。
正是这点,可能会让你写出无效的代码,有时候,这样的无效代码会是一个隐藏很深的Bug。
看下面代码:
private static int TestIntReturnBelowFinally()
{
int i;
try
{
i = ;
}
finally
{
i = ;
Console.WriteLine("\t将int结果改为2,finally执行完毕");
}
return i;
}
返回值是2。
但是:
private static int TestIntReturnInTry()
{
int i;
try
{
return i = ;
}
finally
{
i = ;
Console.WriteLine("\t将int结果改为2,finally执行完毕");
}
}
返回值是1。
再看下面代码:
static User TestUserReturnInTry()
{
User user = new User() { Name = "Mike", BirthDay = new DateTime(, , ) };
try
{
return user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(, , );
Console.WriteLine("\t将user.Name改为Rose");
}
}
user类:
class User
{ public string Name { get; set; } public DateTime BirthDay { get; set; }
}
TestUserReturnInTry方法返回的User中,Name的值已经改为Rose了。
现在来解释为什么上面3个函数会有3种结果。查看TestIntReturnBelowFinally的finally部分的IL代码:
finally
{
IL_0004: ldc.i4.2
IL_0005: stloc.0
IL_0006: ldstr bytearray ( 5C 6E D3 7E 9C // ...\i.n.t..~.g9e
3A 4E 0C FF 6E 6C // :N2...f.i.n.a.l.
6C 4C 8C 5B D5 6B ) // l.y.gbL..[.k
IL_000b: call void [mscorlib]System.Console::WriteLine(string)
IL_0010: endfinally
} // end handler
IL_0011: ldloc.0
IL_0012: ret
}
“IL_0004: ldc.i4.2”首先将2压入栈顶
“IL_0005: stloc.0”将最顶层堆栈的值,也就是2赋值给本地变量,也就是 i (index 0)
“IL_0011: ldloc.0”将本地变量 i (index 0)的值再次压入栈
“IL_0012: ret”结束函数,同时把栈内的返回值压入调用者的栈中。就函数将2赋值给了返回值。
看方法TestIntReturnInTry()的Debug版本的IL代码:
.method private hidebysig static int32 TestIntReturnInTry() cil managed
{
// 代码大小 27 (0x1b)
.maxstack
.locals init ([] int32 i,
[] int32 CS$$)
IL_0000: nop
.try
{
IL_0001: nop
IL_0002: ldc.i4.1
IL_0003: dup
IL_0004: stloc.0
IL_0005: stloc.1
IL_0006: leave.s IL_0018
} // end .try
finally
{
IL_0008: nop
IL_0009: ldc.i4.2
IL_000a: stloc.0
IL_000b: ldstr bytearray ( 5C 6E D3 7E 9C // ...\i.n.t..~.g9e
3A 4E 0C FF 6E 6C // :N2...f.i.n.a.l.
6C 4C 8C 5B D5 6B ) // l.y.gbL..[.k
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: nop
IL_0016: nop
IL_0017: endfinally
} // end handler
IL_0018: nop
IL_0019: ldloc.1
IL_001a: ret
} // end of method Program::TestIntReturnInTry
TestIntReturnInTry在IL中创建了两个本地变量 i 和CS$1$0000 ,i 存储的是1,然后finally中 i 被赋值为2。调用者真正得到的是由IL创建的CS$1$0000所对应的值。用Reflector查看C#代码:
private static int TestIntReturnInTry()
{
int i;
int CS$$;
try
{
CS$$ = i = ;
}
finally
{
i = ;
Console.WriteLine("\t将int结果改为2,finally执行完毕");
}
return CS$$;
}
实际上,finally中i=2没有任何意义,所以在本函数的release版本中,IL中找不到对应的代码:
.method private hidebysig static int32 TestIntReturnInTry() cil managed
{
// 代码大小 17 (0x11)
.maxstack
.locals init ([] int32 CS$$)
.try
{
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: leave.s IL_000f
} // end .try
finally
{
IL_0004: ldstr bytearray ( 5C 6E D3 7E 9C // ...\i.n.t..~.g9e
3A 4E 0C FF 6E 6C // :N2...f.i.n.a.l.
6C 4C 8C 5B D5 6B ) // l.y.gbL..[.k
IL_0009: call void [mscorlib]System.Console::WriteLine(string)
IL_000e: endfinally
} // end handler
IL_000f: ldloc.0
IL_0010: ret
} // end of method Program::TestIntReturnInTry
用Reflector查看release版本中C#代码:
private static int TestIntReturnInTry()
{
int CS$$;
try
{
CS$$ = ;
}
finally
{
Console.WriteLine("\t将int结果改为2,finally执行完毕");
}
return CS$$;
}
再解释第三个方法TestUserReturnInTry为什么返回的是“Rose”。Reflector查看release版本中C#代码:
private static User TestUserReturnInTry()
{
User CS$$;
User <>g__initLocal0 = new User {
Name = "Mike",
BirthDay = new DateTime(0x7da, , )
};
User user = <>g__initLocal0;
try
{
CS$$ = user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(0x7da, , );
Console.WriteLine("\t将user.Name改为Rose");
}
return CS$$;
}
User是引用类型, CS$1$0000 = user;说明CS$1$0000和user指向的是同一个对象,当在finally中 user.Name = "Rose"时CS$1$0000的Name也会变为“Rose”。所以返回的CS$1$0000的Name为“Rose”。
再举一个例子:
private static User TestUserReturnInTry2()
{
User user = new User() { Name = "Mike", BirthDay = new DateTime(, , ) };
try
{
return user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(, , );
user = null;
Console.WriteLine("\t将user置为anull");
}
}
返回的结果不是null,而一个Name=“Rose”,BirthDay = new DateTime(2010, 2, 2)的User对象。Reflector查看release版本中C#代码:
private static User TestUserReturnInTry2()
{
User CS$$;
User <>g__initLocal1 = new User {
Name = "Mike",
BirthDay = new DateTime(0x7da, , )
};
User user = <>g__initLocal1;
try
{
CS$$ = user;
}
finally
{
user.Name = "Rose";
user.BirthDay = new DateTime(0x7da, , );
user = null;
Console.WriteLine("\t将user置为anull");
}
return CS$$;
}
CS$1$0000和user指向的是同一个对象,当在finally中 user=null 时,只是user指向为null了,CS$1$0000指向的对象并没有变。
转自:《编写高质量代码改善C#程序的157个建议》陆敏技
编写高质量代码改善C#程序的157个建议——建议61:避免在finally内撰写无效代码的更多相关文章
- 编写高质量代码改善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< ...
随机推荐
- Linux 驱动层实现阻塞和非阻塞
linux应用层的函数默认是阻塞型的,但是要想真正实现阻塞,还需要驱动的支持才行. 例:open().scanf().fgets().read().accept() 等 1.默认情形,驱动层不实现阻塞 ...
- java排序。。简单的冒泡排序
总结:一种简单的交换顺序,从数左边开始扫描待排序的元素,在扫描过程中依次对相邻元素进行比较,将较大值后移,每经过一轮排序后,值最大的元素将移到末尾, 此时记下该元素的位置,下一轮排序只需比较到此位置即 ...
- AngularJS:template2
ylbtech-AngularJS: 1.返回顶部 1. 2. 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 1. 2. 6.返回顶部 作者:ylbtech出处:h ...
- 1116 Come on! Let's C
题意:略. 思路:略. 代码: #include <cstdio> #include <cmath> ; }; bool isPrime(int n) { ) return f ...
- 静态方法staticmethod类方法classmethod
静态方法 只是名义上归类管理,实际上在静态方法里访问不了类或者实例中的任何属性. 类方法 只能访问类变量,不能访问实例变量 属性方法 把一个方法变成一个静态属性,调用的时候不能加() 如果这种属性方法 ...
- windows环境下,安装zookeeper~
1. 概述 ZooKeeper是Hadoop的正式子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护.名字服务.分布式同步.组服务等.ZooKeeper的目标就是封装好复杂 ...
- 010. VS2015创建MVC项目
1. 文件→新建→项目 2.选择空模板→选中MVC 3. 预览(如果是使用aspx, 则可以删除Views中的web.config, 如果使用Razor则不要删除, 否则会报Views/Home/In ...
- CVE-2017-8464(震网三代)复现
开启msf root@sch01ar:~# msfconsole 设置模块 msf > use exploit/windows/fileformat/cve_2017_8464_lnk_rce ...
- java成神之——HttpURLConnection访问api
HttpURLConnection 访问get资源 访问post资源 访问Delete资源 获取状态码 结语 HttpURLConnection 访问get资源 HttpURLConnection c ...
- left join的多重串联与groupby
有三张表或组合查询,f1,f2,f3,其中,f1分别与f2,f3是一对多关系,f1一条记录可能对应f2或f3中0条或多条记录 要创建一个查询,以f1为基准,即f1中有多少条记录,结果也就返回对应数量的 ...