减少C#中的重复错误处理代码?
我从未对异常处理的方式完全满意,有很多异常,并且try / catch带到表中(堆栈展开等),但似乎在此过程中破坏了很多OO模型。
无论如何,这是问题所在:
假设我们有一些包装或者包含网络文件IO操作的类(例如,在某处的特定UNC路径处读写文件)。由于各种原因,我们不希望这些IO操作失败,因此,如果检测到它们失败,则请重试它们,并继续尝试它们,直到它们成功或者达到超时为止。我已经有一个方便的RetryTimer类,可以实例化该类,并使用它在重试之间使当前线程休眠,并确定超时时间已过,等等。
问题是我们在此类的几种方法中都有大量的IO操作,并且需要将每个操作都包装在try-catch / retry逻辑中。
这是一个示例代码片段:
RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10)); bool success = false; while (!success) { try { // do some file IO which may succeed or fail success = true; } catch (IOException e) { if (fileIORetryTimer.HasExceededRetryTimeout) { throw e; } fileIORetryTimer.SleepUntilNextRetry(); } }
因此,如何避免在整个类中为每个文件IO操作复制大部分代码?我的解决方案是使用匿名委托块和该类中执行传递给它的委托块的单个方法。这使我可以通过其他方法执行以下操作:
this.RetryFileIO( delegate() { // some code block } );
我有点喜欢,但是还有很多不足之处。我想听听其他人如何解决这类问题。
解决方案
回答
只是想知道,我们觉得方法有什么不足之处?我们可以将匿名委托替换为..名称?委托,像
public delegate void IoOperation(params string[] parameters); public void FileDeleteOperation(params string[] fileName) { File.Delete(fileName[0]); } public void FileCopyOperation(params string[] fileNames) { File.Copy(fileNames[0], fileNames[1]); } public void RetryFileIO(IoOperation operation, params string[] parameters) { RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10)); bool success = false; while (!success) { try { operation(parameters); success = true; } catch (IOException e) { if (fileIORetryTimer.HasExceededRetryTimeout) { throw; } fileIORetryTimer.SleepUntilNextRetry(); } } } public void Foo() { this.RetryFileIO(FileDeleteOperation, "L:\file.to.delete" ); this.RetryFileIO(FileCopyOperation, "L:\file.to.copy.source", "L:\file.to.copy.destination" ); }
回答
这似乎是一个很好的机会,可以看看面向方面的编程。这是一篇有关.NET中AOP的好文章。通常的想法是,我们将跨功能问题(即重试x个小时)提取到一个单独的类中,然后为需要以这种方式修改其行为的任何方法添加注释。它的外观如下(在Int32上具有不错的扩展方法)
[RetryFor( 10.Hours() )] public void DeleteArchive() { //.. code to just delete the archive }
回答
我们还可以使用更多面向对象的方法:
- 创建一个执行错误处理并调用抽象方法执行具体工作的基类。 (模板方法模式)
- 为每个操作创建具体的类。
这具有命名我们执行的每种类型的操作的优点,并为我们提供了一个命令模式,这些操作已被表示为对象。
回答
这是我最近所做的。它可能在其他地方做得更好,但是看起来很干净并且可重用。
我有一个看起来像这样的实用程序方法:
public delegate void WorkMethod(); static public void DoAndRetry(WorkMethod wm, int maxRetries) { int curRetries = 0; do { try { wm.Invoke(); return; } catch (Exception e) { curRetries++; if (curRetries > maxRetries) { throw new Exception("Maximum retries reached", e); } } } while (true); }
然后在我的应用程序中,我使用c#的Lamda表达式语法使事情保持整洁:
Utility.DoAndRetry( () => ie.GoTo(url), 5);
这将调用我的方法,并最多重试5次。在第五次尝试时,原始异常将在重试异常内被抛出。