使用 Using 块
解决的问题
设置变量并将其拆除是一种非常常见的模式 - 虽然我们可以使用 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 语句更精确来解决的那样。
仅当您能采取某些措施修复异常时才处理异常。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~