“使用例外控制流程”的示例

“使用exception来控制流”的代码是什么样的? 我试图找到一个直接的C#示例,但不能。 为什么不好?

谢谢

下面的代码捕获了一个可以轻松完全避免的exception。 这使代码更难以遵循,并且通常也会产生性能成本。

int input1 = GetInput1(); int input2 = GetInput2(); try { int result = input1 / input2; Output("{0} / {1} = {2}", input1, input2, result); } catch (OverflowException) { Output("There was an overflow exception. Make sure input2 is not zero."); } 

更好

此代码检查引发exception的条件,并在错误发生之前更正该情况。 这样就没有例外。 代码更具可读性,性能很可能更好。

 int input1 = GetInput1(); int input2 = GetInput2(); while (input2 == 0) { Output("input2 must not be zero. Enter a new value."); input2 = GetInput2(); } int result = input1 / input2; Output("{0} / {1} = {2}", input1, input2, result); 

根据定义,exception是在软件正常流程之外发生的事件。 我头脑中的一个简单示例是使用FileNotFoundException来查看文件是否存在。

 try { File.Open(@"c:\some nonexistent file.not here"); } catch(FileNotFoundException) { // do whatever logic is needed to create the file. ... } // proceed with the rest of your program. 

在这种情况下,您没有使用File.Exists()方法,该方法实现了相同的结果,但没有exception的开销。

除了不良用法之外,还存在与exception相关的开销,填充属性,创建堆栈跟踪等。

它大致相当于goto,除了Exception这个词更糟糕,并且开销更大。 你告诉代码跳转到catch块:

 bool worked; try { foreach (Item someItem in SomeItems) { if (someItem.SomeTestFailed()) throw new TestFailedException(); } worked = true; } catch(TestFailedException testFailedEx) { worked = false; } if (worked) // ... logic continues 

如你所见,它正在运行一些(补偿)测试; 如果它们失败,则抛出exception,并将worked设置为false

当然,更容易直接更新bool worked

希望有所帮助!

这是一个常见的:

 public bool TryParseEnum(string value, out T result) { result = default(T); try { result = (T)Enum.Parse(typeof(T), value, true); return true; } catch { return false; } } 

可能是我见过的最严重的违规行为:

 // I haz an array... public int ArrayCount(object[] array) { int count = 0; try { while (true) { var temp = array[count]; count++; } } catch (IndexOutOfRangeException) { return count; } } 

我目前正在使用第三方程序执行此操作。 它们有一个“游标”界面(基本上是一个IEnumerable替代品),其中告诉程序你完成的唯一方法是引发exception。 代码基本上看起来像:

 // Just showing the relevant section bool finished = false; public bool IsFinished() { return finished; } // Using something like: // int index = 0; // int count = 42; public void NextRecord() { if (finished) return; if (index >= count) throw new APIProgramSpecificException("End of cursor", WEIRD_CONSTANT); else ++index; } // Other methods to retrieve the current value 

毋庸置疑,我讨厌 API – 但它是流量控制exception(以及疯狂的工作方式)的一个很好的例子。

我不喜欢C#,但你可以看到try-catch-finally语句和if-then-else的正常控制流语句之间有一些相似之处。

想一想,每当throwexception时,都会强制将控件传递给catch子句。 所以,如果你有

 if (doSomething() == BAD) { //recover or whatever } 

您可以通过try-catch轻松地想到它:

 try { doSomething(); } catch (Exception e) { //recover or do whatever } 

关于exception的强大之处在于,您不必在同一个主体中来改变程序的流程,您可以随时抛出exception,并保证控制流将突然发散并到达catch子句。 这很强大但同时也很危险,因为你可以完成最后需要备份的操作,这就是finally语句存在的原因。

此外,您还可以模拟while语句,而无需有效地使用它的条件:

 while (!finished) { //do whatever } 

可以变成

 try { while (true) { doSomethingThatEventuallyWillThrowAnException(); } } catch (Exception e) { //loop finished } 

由合作伙伴开发的模块导致我们的应用程序需要很长时间才能加载。 经过仔细研究,该模块正在寻找app启动时的配置文件。 这本身并不太令人反感,但它的做法却非常糟糕:

对于app目录中的每个文件,它打开文件并尝试将其解析为XML。 如果一个文件抛出exception(因为它不是XML),它会捕获exception,压制它,并尝试下一个文件!

当合作伙伴测试此模块时,他们在app目录中只有3个文件。 bonehead配置文件搜索对测试应用启动没有明显影响。 当我们将它添加到我们的应用程序时,app目录中有100个文件,并且应用程序在启动时冻结了将近一分钟。

为了向伤口添加盐,模块正在搜索的配置文件的名称是预先确定的并且是恒定的。 不需要任何类型的文件搜索。

天才有其局限性。 愚蠢是无限的。

一个例子是使用exception从递归方法返回结果:

 public void Search(Node node, object data) { if(node.Data.Equals(data)) { throw new ResultException(node); } else { Search(node.LeftChild, data); Search(node.RightChild, data); } } 

做这样的事情有几个原因是个问题。

  1. 这完全是违反直觉的。 例外是针对特殊情况而设计的。 按预期工作的东西应该(我们希望)永远不会是一个特例。
  2. 您不能总是依赖抛出并传播给您的exception。 例如,如果抛出exception的代码在单独的线程中运行,则需要一些额外的代码来捕获它。
  3. 这是一个潜在的性能问题。 有一些与exception相关的开销,如果你抛出很多exception,你可能会发现应用程序性能下降。

这里有一些关于这个主题的例子和一些有趣的讨论。

免责声明:上面的代码改编自该维基页面上的第一个示例,将其转换为C#。