前两天做项目,在使用MySqlDataReader时遇到了问题,后来发现是遇到异常时没有关闭myReader导致的。要解决那个问题就要明白try—catch—finally的用法了。
1. 为什么要用finally??因为异常进入catch后有时需要直接返回,不继续执行后面的语句,但是还有一部分工作要做(比如释放资源之类的)这时就要写到finally里面。
2. 说明:
try
{
//可能发生异常的代码。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容
}
catch
{
//除非try里面执行代码发生了异常,否则这里的代码不会执行
//因为异常了,有时直接在这里就return了
}
finally
{
//不管什么情况都会执行,包括try或 catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally
}
3. 用途举例:我们可以在try块中尝试打开数据库,然后读取数据,然后直接把得到的数据return出去,关闭数据连接的工作就交给finally来做——finally中先判断数据库是否正常打开了,打开了就关闭。这样代码写起来很清晰,每个部分各做各的事。这样我们也可以非常肯定的说,无论发生什么情况(只要不是进程被强行杀掉),finally中的内容一定是要执行的。
4. finally块中是不允许写return的,如果一定要写,就会得到一个编译期错误,error CS0157:Control cannot leave the body of a finally clause
5. 先return?先finally?
既然finally一定是要执行的,即使try块中有return,那么这两者的执行顺便是怎么样的呢?简单做个实验
using System;
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func1());
}
public static int Func1()
...{
int a = 1;
try
...{
return a;
}
finally
...{
a++;
}
}
}
结果为“1”。那么看上去是执行return在先,而finally在后了。真的是这样吗?
例子中我要return的a是一个值类型,那么如果是引用类型,结果又会如何呢?
using System;
public class TestClass2
...{
public int value = 1;
}
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func2().value);
}
public static TestClass2 Func2()
...{
TestClass2 t = new TestClass2();
try
...{
return t;
}
finally
...{
t.value++;
}
}
}
这一次运行的结果是
2
。显然,运行
Func2()
返回的结果并不直接是
return
后面写的
t
,而是经过
finally
块执行后值发生变化的
t
。如何来解释这种区别呢?
3 CLR 的栈
要解释这种区别,就需要看看其 IL 是什么,从调用函数、参数栈的角度来理解。 CLR 在执行中也有栈,但这个栈的用途与传统的本地代码中的栈并不完全相同。本地代码中栈的用处非常大,不但可以用来临时保存寄存器的值,还用来保存局部变量,此外还用来保存部分或全部传给函数的参数,而函数的返回值一般是通过 EAX 寄存器来传递的,而不是用栈。但在 CLR 中,局部变量并非显式的用栈来保存,栈只是用来调用函数时传递参数,此外,函数的返回值也是用栈来保存的。当调用一个函数时,将函数所需要的参数依次压栈,函数里面直接取用这些参数,在函数返回时将返回值压栈,函数返回后,栈顶即是返回值。如果调用者并不关心返回值,那么需要执行一下 pop 语句,把返回值弹出,这样保证函数在调用前后栈顶的位置是相同的。
当通过压栈传递参数时,参数的类型不同,压栈的内容也不同。 如果是值类型,压栈的就是经过复制的参数值,如果是引用类型,那么进栈的只是一个引用,这也就是我们所熟悉的,传递值类型时,函数内修改参数值不会影响函数外,而引用类型的话则会影响。 代码中当我们执行 new 时,对应的 IL 是 newobj ,其结果是创建一个 TestClass2 类型的对像并返回一个引用放置于栈上,之后的 stloc 就将这个引用保存为局部变量,于是栈上没有了其他内容。 Try 块并没有执行太多操作,只是把刚保存的引用再放到栈上,再保存为另一个局部变量,这个局部变量就是稍后要返回的引用,此时我们拥有两个局部变量,但它们是指向同一个对象的两个引用。 Finally 块先拿出开始时保存的引用放到栈上, dup 语句使得栈顶再增加一个完全一样的引用,之后 ldfld 语句是从栈顶对象取一个成员放到栈上,所取的成员是 value ,之后再往栈上压一个 1 ,再执行 add ,就实现了 1+1=2 的过程, add 从栈上弹出两个值,再向栈压回一个值。此时再调用 stfld 就把刚刚压栈的 2 设置给栈上 2 之下的那个引用所指对象的 value 属性上。而在 finally 之后的部分才是真正的 return ,它试图取出我们所保存的第二个局部变量压栈,将它作为返回值。但对于引用类型来说,它与先前所操作的引用所指的是同一对象,因此 finally 块中的操作会影响到返回值,也就非常好理解了。