在异常处理中使用 Finally 块

发布日期:2026-07-14 19:35:03   来源 : 杭州电子商务研究院    浏览量 :12
杭州电子商务研究院 发布日期:2026-07-14 19:35:03  
12

解决的问题

虽然从异常中恢复的性质可能非常具体,但我们希望在每个事件中都发生某些方面 - 无论发生一个异常还是另一个异常,或者函数是否成功而没有任何问题。

我们可以按照以下大致思路来安排我们的代码:

      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;
    
以上内容来自杭州电子商务研究院推送
关注
关于我们
热门推荐
合作伙伴
免责声明:本站部分资讯来源于网络,如有侵权请及时联系客服,我们将尽快处理
Copyright © 2025-2027 ToB产业网址导航 公安备案 浙公网安备33010602013138号 浙ICP备16025413号-9
支持 反馈 关注 数据