关于异常:减少C#中的重复错误处理代码?

关于异常:减少C#中的重复错误处理代码?

Reducing duplicate error handling code in C#?

我从未对异常处理的方式完全满意,有很多异常,并且try / catch带到表中(堆栈展开等),但似乎在此过程中破坏了很多OO模型。

无论如何,这是问题所在:

假设您有一些包装或包含网络文件IO操作的类(例如,在某处的特定UNC路径处读写文件)。由于各种原因,您不希望这些IO操作失败,因此,如果检测到它们失败,则请重试它们,并继续尝试它们,直到它们成功或达到超时为止。我已经有一个方便的RetryTimer类,可以实例化该类,并使用它在重试之间使当前线程休眠,并确定超时时间已过,等等。

问题是您在此类的几种方法中都有大量的IO操作,并且需要将每个操作都包装在try-catch / retry逻辑中。

这是一个示例代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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操作复制大部分代码?我的解决方案是在执行传递给它的委托块的类中使用匿名委托块和单个方法。这使我可以通过其他方法执行以下操作:

1
2
3
4
this.RetryFileIO( delegate()
    {
        // some code block
    } );

我有点喜欢,但是还有很多不足之处。我想听听其他人如何解决这类问题。


这似乎是一个很好的机会,可以看看面向方面的编程。这是一篇有关.NET中AOP的好文章。通常的想法是,您将跨功能问题(即重试x个小时)提取到一个单独的类中,然后为所有需要以这种方式修改其行为的方法添加注释。它的外观如下(在Int32上使用了不错的扩展方法)

1
2
3
4
5
[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
  //.. code to just delete the archive
}

只是想知道,您觉得您的方法有什么不足之处?您可以将匿名委托替换为..名称?委托,像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    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" );
    }


这是我最近所做的。它可能在其他地方做得更好,但是看起来很干净并且可重用。

我有一个实用程序方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    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表达式语法使事情保持整洁:

1
Utility.DoAndRetry( () => ie.GoTo(url), 5);

这将调用我的方法,并最多重试5次。在第五次尝试时,原始异常将在重试异常内重新抛出。


您还可以使用更多面向对象的方法:

  • 创建一个执行错误处理并调用抽象方法执行具体工作的基类。 (模板方法模式)
  • 为每个操作创建具体的类。

这具有命名您执行的每种操作的优点,并为您提供命令模式-操作已被表示为对象。


推荐阅读