使用 Using 块

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

解决的问题

设置变量并将其拆除是一种非常常见的模式 - 虽然我们可以使用 try-finally 方法执行此操作,但我们可以使用 _using _语句更优雅地组织它。

遵循此模式的代码:

      var ourObject = new BusinessObject();

try
{
ourObject.DoStuff();
}
finally
{
ourObject.Dispose();
}
    

可以更简洁地重写如下:

      using (var ourObject = new BusinessObject())
{
outObject.DoStuff();
}
    

我们的场景

当我们为应用程序编写代码时,既然我们知道了它是什么样子,我们发现设置对象、使用对象、然后释放对象的模式无处不。我们到处都使用 try-finally 来实现这一点。有一天,在代码审查中,另一位开发人员说:“try-finally 结构没有问题但你有没有考虑过将变量包装在 _using _statement 中?”

经过一番挖掘,你会发现 using 结构接受一个变量,创建一个本地作用域来处理该变量,然后在执行离开 using 语句时对其调用 Dispose() 方法。因此,我们可以重构代码的 try-finally 部分:

      try
{
priceClient.Connect();

       var updatedPrice = priceClient.GetPrice();

return updatedPrice;
}
/// catch blocks
finally
{
    priceClient.ForceClose();
}
    

看起来像这样:

      using (var priceClient = new PriceClient())
{
    priceClient.Connect();

    var updatedPrice = priceClient.GetPrice();

    return updatedPrice;
}
    

或者我们可以吗?将 priceClient 变量放在 using 块内意味着将对其调用 Dispose() - 这到底是什么意思?我的意思是,强制关闭套接字似乎是在处理对象时应该做的事情,但我们需要更确定这里发生了什么。

实现 IDisposable

文档说 using 块中的内容必须实现 IDisposable - 什么IDisposable 以及如何实现它?从形式上讲,IDisposable 是 System 命名空间中的一个接口 - 非常核心和必不可少的内容。IDisposable 实际上是最简单的接口,无论如何也是最简单的有意义的接口 - 它有一个 void 方法:

      public void Dispose ();
    

这就是 using 块调用的 Dispose 方法的来源——因为 using 块中的任何变量都保证具有 Dispose,所以编译器知道我们可以调用它。关键点:

Dispose() 方法具体处理什么完全由开发人员决定。

如果您以为仅仅因为我们实现了一个特定接口并将其命名为 Dispose ,就认为存在某种框架或资源管理魔法,那您就错了。Dispose 方法只是一个约定好的放置清理逻辑的地方——开发人员需要确保清理逻辑按预期执行。这只是,仅仅因为您在 PriceClient 上调用 dispose ,并不意味着它会调用 ForceClose 或以其他方式正确关闭连接。

幸运的是,由于您的供应商的库是开源的,您可以查看对象上 Dispose 的实际内容:

      class PriceClient: IDisposable
{
    public void Dispose()
    {
        this.ForceClose();
    }
    

这意味着此代码:

      using (var priceClient = new PriceClient())
{
    priceClient.Connect();

    var updatedPrice = priceClient.GetPrice();

    return updatedPrice;
}
    

做和这个完全相同的事情:

      try
{
    priceClient.Connect();

    var updatedPrice = priceClient.GetPrice();

    return updatedPrice;
}
finally
{
    priceClient.ForceClose();
}
    

但只是因为我们已经验证了 Dispose() 调用了 ForceClose。如果我们没有验证,或者它没有验证,我们就不能确定这两个语句是否等价。

在 Using 块中捕获异常

我们原来的结构中的 catch 块怎么样?

      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;
}
    

我们的 using 块已覆盖错误处理的 try 和 finally 部分,但未覆盖 catch 块。using 语句在这方面没有提供任何帮助 - 要捕获异常,我们需要在 using 语句中放置一个 try-catch 结构:

      using (var priceClient = new PriceClient())
{
    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 的构造函数中发生异常怎么办?此异常可能发生在我们原始错误处理之外:

      var priceClient = new PriceClient();

try
{
    priceClient.Connect();

    var updatedPrice = priceClient.GetPrice();

    return updatedPrice;
}
    

也就是说,如果异常发生在 try 块之前,它将不会被捕获。在我们的 using 语句中也是如此——如果初始化 using 变量时发生异常:

      using (var priceClient = new PriceClient())
{
    

该对象尚未实例化,因此您无法对其调用 Dispose。 所有这些都导致了一个原则:

在构造函数中抛出异常时要非常小心。

您不能简单地说“永远不要在构造函数中抛出异常”——您仍然需要某种方式向调用堆栈发出信号,告知存在问题。如果您发现自己在使用这种方法时遇到问题,请考虑使用静态工厂模式来创建对象。

没有 Catch 块意味着什么

在进行技术面试时,我会问一个问题来确定应聘者对结构错误处理的理解程度:“在审查一些代码时,你发现一个 try-finally 块,里面没有 catch 块。编写这段代码的开发人员的意图是什么?”

我经常听到有人说“你应该总是有一个 catch 块”,有时还会有人说“我认为这甚至不会编译”。没有 catch 的 try-finally 是完全有效的- 这意味着当前执行范围无法解决异常,只会将其传递到链上。如果您已经实现了我们在第二个目标中讨论的顶级错误处理模式,它将被处理并可能记录在那里,以便开发人员可以弄清楚如何处理它。

一个很好的例子是当应用程序的关键资源(如数据库)不可用时。如果我有一个网页正在调用数据库,我无法从这种情况中恢复——我只需要显示一个错误页面并完成它,我可以让顶级错误处理程序来做。如果我尝试在低级例程中处理该异常,在最好的情况下,我将返回 null,就像我们在第一个目标中看到的那样,而在最坏的情况下,我将隐藏一个问题,就像我们在第二个目标中通过使我们的 catch 语句更精确来解决的那样。

仅当您能采取某些措施修复异常时才处理异常。

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