在异常处理中使用 Finally 块
解决的问题
虽然从异常中恢复的性质可能非常具体,但我们希望在每个事件中都发生某些方面 - 无论发生一个异常还是另一个异常,或者函数是否成功而没有任何问题。
我们可以按照以下大致思路来安排我们的代码:
public static void OurProcedure()
{
// set up stuff to get ready to act in our code
try
{
// perform actions that may create exceptions
}
catch(ExceptionType1 exception1)
{
// repair the situation when ExceptionType1 occurs
// using the details available in the exception1 variable
}
catch (ExceptionType2)
{
// repair the situation when ExceptionType2 occurs
// This exception type doesn't have any details which are meaningful
// so we don't bother with an exception variable
}
finally
{
// tear down anything that needs to be disassembled
// or release any resources that are no longer needed
}
}
我们首先在 try 块外执行一些异常安全设置。这将使变量可用于所有后面的 catch 和 finally 作用域块。然后我们在 try 块中采取行动- 执行为函数提供值的操作。每个 catch 块处理我们想象可能出现的问题,最后 - FINALLY 块清理操作完成后不必要的资源,无论操作成功还是失败。
我们的场景
我们继续努力让供应商的服务器完全可靠——我们仍然每周都会收到一次 ConnectionFailedException——如果你仔细想想,这其实并不那么糟糕。但供应商库的实现方式存在问题。
如果过程顺利进行:
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
套接字(一种网络连接)通过 Connect 函数打开,并在 GetPrice 结束时关闭。但是,当在 GetPrice 中发生 ConnectionFailedException 时,套接字永远不会关闭 - 这意味着随着时间的推移,这些无效连接会堆积起来,您的 IT 伙伴必须每隔几周重新启动路由器才能关闭它们。
碰巧的是,供应商知道这个问题,所以他们在他们的库中提供了一种方法来解决这个问题:
priceClient.ForceClose();
我们最初的想法是将这段代码放入导致问题的 catch 块中:
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
priceClient.ForceClose();
return null;
}
但有些事情困扰着我们——连接在正常情况下关闭了,对吧?我们是这样认为的,但我们 100% 确定吗?此外,与此同时,我们的顶级错误处理程序捕获了一些 ConnectionTimeoutException,这些异常不是完全连接失败,而是连接成功但响应时间太长:
catch (TimeoutException timeout)
{
return null;
}
我想如果我们要在各处处理这个问题,我们需要在各处复制并粘贴 ForceClose() 调用——并且我们知道复制和粘贴几乎不是正确的答案。
这就是 finally 块的作用。finally 块是无论是否发生异常都会始终执行的代码。
为了使我们的 ForceClose() 调用在所有情况下执行 - 满意路径和所有 catch 块,我们将其添加到末尾,如下所示:
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
return null;
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
return null;
}
finally
{
priceClient.ForceClose();
}
为什么不像这样简单地在错误处理之外添加 ForceClose() 呢?
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
return null;
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
return null;
}
priceClient.ForceClose();
问题在于 catch 块中的那些返回语句。当执行到达这些点时,它会绕过过程中剩余的所有内容 - 包括其他 catch 块。包括 finally 块可确保您的清理代码将被执行。
无法在 Finally 块中返回
在这些 catch 块中,您经常做的另一件事是返回值 – 这就是简单地将 ForceClose() 放在末尾的问题。也许我们应该重新组织我们的代码,只在 finally 块中返回值,如下所示:
var priceClient = new PriceClient();
decimal? updatedPrice = null;
try
{
priceClient.Connect();
updatedPrice = priceClient.GetPrice();
}
catch(InvalidCastException)
{
updatedPrice = decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
// do nothing because updatedPrice is already null
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
// do nothing because updatedPrice is already null
}
finally
{
priceClient.ForceClose();
return updatedPrice;
}
如果我们这样做,我们会得到一个编译器错误:
控制不能离开 finally 子句的主体
禁止从 finally 子句返回值,因为它会导致函数的返回值发生冲突的情况:
try
{
return 1;
}
finally
{
return 2;
}
为了获得我们想要的风格,将控制流和逻辑干净地分开,我们只需将 return 语句放在finally 块之后的最最后:
decimal? updatedPrice = null;
try
{
priceClient.Connect();
updatedPrice = priceClient.GetPrice();
}
catch(InvalidCastException)
{
updatedPrice = decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
// do nothing because updatedPrice is already null
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
// do nothing because updatedPrice is already null
}
finally
{
priceClient.ForceClose();
}
return updatedPrice;
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~