Effective C#之Item 45:Prefer the Strong Exception Guarantee

  rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Chapter 6. Miscellaneous

第六章 杂项

Some items don't fit convenient categories. But that does not limit their importance. Understanding code access security is important for everyone, as is understanding exception-handling strategies. Other recommendations are constantly changing because C# is a living language, with an active community and an evolving standard. It pays to look forward to these changes and prepare for them; they will impact your future work.

一些条款不适合任何目录,但是却不限制它们的重要性。理解代码访问安全性对每个人来说是很重要的,就像理解异常处理策略一样。其他的建议是在不断的变化的,因为C#是个在不断成长的语言,伴随着不断的交流和进化标准。提前对待这些变化并做准备,它们会影响你未来的工作。


Item 45: Prefer the Strong Exception Guarantee

优先选择强异常保障

When you throw an exception, you've introduced a disruptive event into the application. Control flow has been compromised. Expected actions did not occur. Worse, you've left the cleanup operation to the programmer writing the code that eventually catches the exception. The actions available when you catch exceptions are directly related to how well you manage program state when an exception gets thrown. Thankfully, the C# community does not need to create its own strategies for exception safety; the C++ community did all the hard work for us. Starting with Tom Cargill's article "Exception Handling: A False Sense of Security," and continuing with writings by Herb Sutter, Scott Meyers, Matt Austern, Greg Colvin, and Dave Abrahams, the C++ community developed a series of best practices that we can adapt to C# applications. The discussions on exception handling occurred over the course of 6 years, from 1994 to 2000. They discussed, debated, and examined many twists on a difficult problem. We should leverage all that hard work in C#.

当你抛出异常时,就给应用程序引入了一个分裂性的事件。控制流已经被折中了。期望的行为并没有发生。更糟糕的是,你将清理操作留给了编写代码的程序员,他们逐步的捕捉异常。当你在捕捉异常时采取的有效行动都直接和异常抛出时你将程序状态管理的如何相关。谢天谢地,C#社区不需要创建自己的异常安全策略;C++社区为我们做了所有艰难的工作。从Tom Cargill's的文章"Exception Handling: A False Sense of Security,"开始,Herb Sutter, Scott Meyers, Matt Austern, Greg Colvin Dave Abrahams也相继写到这些,C++社区开发出一系列的很好实践,我们可以将其改造到C#应用程序中。关于异常处理的讨论,一直持续了6年,从19942000。他们不断的讨论,争吵,在某个困难的问题上检查很多解决方法。在C#里面我们要利用所有这些艰难的工作。

Dave Abrahams defined three exception-safe guarantees: the basic guarantee, the strong guarantee, and the no-throw guarantee. Herb Sutter discussed these guarantees in his book Exceptional C++ (Addison-Wesley, 2000). The basic guarantee states that no resources are leaked and all objects are in a valid state after your application throws an exception. The strong exception guarantee builds on the basic guarantee and adds that if an exception occurs, the program state did not change. The no-throw guarantee states that an operation never fails, from which it follows that a method does not ever throw exceptions. The strong exception guarantee provides the best trade-off between recovering from exceptions and simplifying exception handling.

Dave Abrahams定义了三个异常安全的保障机制:基本保障,强保障,无抛出保障。Herb Sutter在他的书籍《Exceptional C++ (Addison-Wesley, 2000)》里面讨论了这3个保障。基本保障表述了:没有资源会被泄露,在应用程序抛出异常后,所有的对象都处于有效状态。强异常保障构建在基本保障的基础上,并有所增加:如果发生了异常,程序状态不能改变。无抛出保障表述了:一个操作从不会失败,即,一个方法从不会抛出异常。强保障在由异常中恢复以及简化异常处理之间,提供了最好的折中。

The basic guarantee happens almost by default in .NET and C#. The environment handles memory management. The only way you can leak resources due to exceptions is to throw an exception while you own a resource that implements IDisposable. Item 18 explains how to avoid leaking resources in the face of exceptions.

.NetC#里面,基本保障几乎是默认的发生。环境处理了内存管理。你唯一可以通过异常泄露资源的方式就是当你拥有一个实现了IDisposable接口的资源抛出异常时。Item 18解释了在面对异常时如何避免泄露资源。

The strong guarantee states that if an operation terminates because of an exception, program state remains unchanged. Either an operation completes or it does not modify program state; there is no middle ground. The advantage of the strong guarantee is that you can more easily continue execution after catching an exception when the strong guarantee is followed. Anytime you catch an exception, whatever operation was attempted did not occur. It did not start, and it did not make some changes. The state of the program is as though you did not start the action.

强保障表述了:如果一个操作因为异常而终止的话,程序状态要保持不变。要么操作要完成,要么就不修改程序的状态;没有中间状态。强保障的优势在于,如果你遵守的话,在捕获异常后,你可以更容易的继续执行下去。任何时间你捕获一个异常,不管任何想要执行的操作都不会发生。它不会再开始,不做任何修改。程序的状态就像你什么也没做过一样。

Many of the recommendations I made earlier will help ensure that you meet the strong exception guarantee. Data elements that your program uses should be stored in immutable value types (see Items 6 and 7). If you combine those two items, any modification to program state can easily take place after performing any operation that might throw an exception. The general guideline is to perform any data modifications in the following manner:

前面我给出的很多建议都将帮助保证你满足强异常保障。你的程序使用的数据元素应该存储在不可变值类型中( Item6Item7)。如果你合并这2项,那么在执行任何可能抛出异常的操作后,任何对程序状态做得修改都可以简单的发生。常规的原则是让任何数据的修改都遵守下面的原则:

Make defensive copies of data that will be modified.

Perform any modifications to these defensive copies of the data. This includes any operations that might throw an exception.

Swap the temporary copies back to the original. This operation cannot throw an exception.

对将要被修改的数据进行防御性拷贝。

在数据的防御性拷贝进行任何修改,包含任何可能抛出异常的操作。

将临时的拷贝数据与元数据进行交换,该错误不会抛出异常。

As an example, the following code updates an employee's title and pay using defensive copy:

作为一个例子,下面的代码,使用防御性拷贝的方式更新了雇员的标题和薪酬:

  1. public void PhysicalMove( string title, decimal newPay )
  2. {
  3.   // Payroll data is a struct:
  4.   // ctor will throw an exception if fields aren't valid.
  5.   PayrollData d = new PayrollData( title, newPay, this.payrollData.DateOfHire );
  6.  
  7.   // if d was constructed properly, swap:
  8.   this.payrollData = d;
  9. }
  10.  

Sometimes, the strong guarantee is just too inefficient to support, and sometimes you cannot support the strong guarantee without introducing subtle bugs. The first and simplest case is looping constructs. When the code inside a loop modifies the state of the program and might throw an exception, you are faced with a tough choice: You can either create a defensive copy of all the objects used in the loop, or you can lower your expectations and support only the basic exception guarantee. There are no hard and fast rules, but copying heap-allocated objects in a managed environment is not as expensive as it was in native environments. A lot of time has been spent optimizing memory management in .NET. I prefer to support the strong exception guarantee whenever possible, even if it means copying a large container: The capability to recover from errors outweighs the small performance gain from avoiding the copy. In special cases, it doesn't make sense to create the copy. If any exceptions would result in terminating the program anyway, it makes no sense to worry about the strong exception guarantee. The larger concern is that swapping reference types can lead to program errors. Consider this example:

有时,要支持强保障的话效率太低了;有时,不引入一些小bug的话,也难支持强保障。第一个也是最简单的情况是循环结构体。当一个循环内部的代码修改程序状态并且可能抛出异常时,你将面对一个艰难的选择:你可以对循环里面使用的所有的对象都创建防御性拷贝;或者可以降低你的期望,仅仅支持基本异常保障。没有固定的或者更快的规则,在托管环境下对基于堆分配的对象进行拷贝并不像在原始环境下一样代价昂贵。.Net里面花费很多时间来优化内存管理。只要可能,我就更愿意支持强类型保障,哪怕意味着拷贝一个大的容器:从错误中恢复的能力比避免拷贝而获得的小性能要重要。在特殊情况下,创建拷贝是讲不通的。如果任何异常都会到这程序的终止的话,就没有必要担心强异常保障了。交换引用类型会一起程序错误,这将是更大的关注点。考虑这个例子:

  1. private DataSet _data;
  2. public IListSource MyCollection
  3. {
  4.   get
  5.   {
  6.     return _data;
  7.   }
  8. }
  9.  
  10. public void UpdateData( )
  11. {
  12.   // make the defensive copy:
  13.   DataSet tmp = _data.Clone( ) as DataSet;
  14.  
  15.   using ( SqlConnection myConnection = new SqlConnection( connString ))
  16.   {
  17.     myConnection.Open();
  18.     SqlDataAdapter ad = new SqlDataAdapter( commandString, myConnection );
  19.     // Store data in the copy
  20.     ad.Fill( tmp );
  21.     // it worked, make the swap:
  22.     _data = tmp;
  23.   }
  24. }

 

This looks like a great use of the defensive copy mechanism. You've created a copy of the DataSet. Then you grab new data from the database and fill the temporary DataSet. Finally, you swap the temporary storage back. It looks great. If anything goes wrong trying to retrieve the data, you have not made any changes.

这看起来是充分利用了防御性拷贝的机制。你创建了一个DataSet的拷贝,然后从数据库里面得到新的数据,填充到临时的DataSet里,最后,对临时存储空间进行交换。看起来很好。如果尝试获得数据的任何尝试失败了,你将不做任何改变。

There's only one problem: It doesn't work. The MyCollection property returns a reference to the _data object (see Item 23). All the clients of this class are left holding references to the original DataSet after you call UpdateData. They are looking at the old view of the data. The swap trick does not work for reference types it works only for value types. Because it is a common operation, there is a specific fix for DataSets. Use the Merge method:

仅仅有一个问题:上面的代码不能工作。MyCollection属性返回了一个对_data对象的引用(Item23)。在你调用UpdateData之后,该类的所有用户都拥有对原来DataSet的引用。他们看到的是原来的数据。对于引用类型,交换过程开了个玩笑;交换只对值类型有用。因为这是一个通用的操作,所以对于DataSets有一种特定的修复方法。使用Merge方法:

  1. private DataSet _data;
  2. public IListSource MyCollection
  3. {
  4.   get
  5.   {
  6.     return _data;
  7.   }
  8. }
  9.  
  10. public void UpdateData( )
  11. {
  12.   // make the defensive copy:
  13.   DataSet tmp = new DataSet( );
  14.   using ( SqlConnection myConnection = new SqlConnection( connString ))
  15.   {
  16.     myConnection.Open();
  17.     SqlDataAdapter ad = new SqlDataAdapter( commandString, myConnection);
  18.     ad.Fill( tmp );
  19.     // it worked, merge:
  20.     _data.Merge( tmp );
  21.   }
  22. }

 

Merging the changes into the current DataSet lets all clients keep a valid reference, and the internal contents of the DataSet are updated.

将改变合并到当前的DataSet,让所有的客户都保持有效的引用,DataSet的内部内容也得到了更新。

In the general case, you cannot fix the problem of swapping reference types while still ensuring that all clients have the current copy of the object. Swapping works for value types only. That should be sufficient, if you're following the advice of Item 6.

在一般情况下,你不能修复引用类型交换的问题,只能保证所有的用户有当前对象的拷贝。交换只对值类型适用。如果你遵循了Item 6,那就足够了。

Last, and most stringent, is the no-throw guarantee. The no-throw guarantee is pretty much what it sounds like: A method satisfies the no-throw guarantee if it is guaranteed to always run to completion and never let an exception leave a method. This just isn't practical for all routines in large programs. However, in a few locations, methods must enforce the no-throw guarantee. Finalizers and Dispose methods must not throw exceptions. In both cases, throwing an exception can cause more problems than any other alternative. In the case of a finalizer, throwing an exception terminates the program without further cleanup.

最后,也是最严格的,是无抛出保障。无抛出保障听起来更像是:如果一个方法总能保障运行完毕并且从不让异常出现在方法之外的话,该方法就满足了无抛出异常。在大型程序里,对所有子程序都这样是不现实的。然而,你一部分地方,方法必须要强制无抛出保障。 FinalizersDispose方法不应该抛出异常。对两者来说,抛出异常会比做其他改变引起更多的问题。对于finalizer,抛出异常将终止程序,而不做进一步的清理。

In the case of a Dispose method throwing an exception, the system might now have two exceptions running through the system. The .NET environment loses the first exception and throws the new exception. You can't catch the initial exception anywhere in your program; it was eaten by the system. This greatly complicates your error handling. How can you recover from an error you don't see?

对于Dispose方法抛出异常的情况,就有可能出现两个异常运行在系统里面的情况。.Net环境放过第一个异常并抛出新的异常。在程序的任何地方你都不能捕获开始的异常;它被系统给吃掉了。这使得你的错误吃力很复杂。你怎么能从没有见过的错误中恢复呢?

The last location for the no-throw guarantee is in delegate targets. When a delegate target throws an exception, none of the other delegate targets gets called from the same multicast delegate. The only way around this is to ensure that you do not throw any exceptions from a delegate target. Let's state that again: Delegate targets (including event handlers) should not throw exceptions. Doing so means that the code raising the event cannot participate in the strong exception guarantee. But here, I'm going to modify that advice. Item 21 showed that you can invoke delegates so that you can recover from exceptions. Not everyone does, though, so you should avoid throwing exceptions in delegate handlers. Just because you don't throw exceptions in delegates does not mean that others follow that advice; do not rely on the no-throw guarantee for your own delegate invocations. It's that defensive programming: You should do the best you can because other programmers might do the worst they can.

最后一个无抛出保障的地方就是委托目标。当一个委托目标抛出异常时,同一个多播委托里面的其他委托目标就不能被调用了。关于这个的唯一方式就是保证委托从不抛出异常。让我们再说一遍:委托目标(包含事件句柄)不应该抛出异常。这样做意味着:产生事件的代码不能参与强异常保障。但是,在这里,我将要修改那条建议。Item 21 告诉你,你可以调用委托,那样的话就可以从异常里面恢复。但是,不是所有的人都可以,因此,你应该在委托句柄里面避免抛出异常。仅仅因为你在委托里面不抛出异常,并不意味着,其他人也遵守该建议;对你自己的委托调用不要依赖于无抛出保障。那是防御式编程:你应该尽力,因为其他程序员可能做得很差。

Exceptions introduce serious changes to the control flow of an application. In the worst case, anything could have happened or not happened. The only way to know what has and hasn't changed when an exception is thrown is to enforce the strong exception guarantee. Then an operation either completes or does not make any changes. Finalizers, Dispose(), and delegate targets are special cases and should complete without allowing exceptions to escape under any circumstances. As a last word, watch carefully when swapping reference types; it can introduce numerous subtle bugs.

异常对于应用程序的控制流,引入了严重的变化。最坏的情况,任何事情都可能发生,什么事情也可能都不发生。当抛出异常时,要知道什么改变了,什么没有改变,唯一的方式就是:强制强异常保障。那样的话,一个操作要么能完成,要么什么也不改变。FinalizersDispose()和委托目标是特殊情况,应该要执行完,不能让异常影响外部。最后一句话,当交换引用类型是,要小心;那可能会引入无数的小bug

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值