“使用例外控制流程”的示例
“使用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的正常控制流语句之间有一些相似之处。
想一想,每当throw
exception时,都会强制将控件传递给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); } }
做这样的事情有几个原因是个问题。
- 这完全是违反直觉的。 例外是针对特殊情况而设计的。 按预期工作的东西应该(我们希望)永远不会是一个特例。
- 您不能总是依赖抛出并传播给您的exception。 例如,如果抛出exception的代码在单独的线程中运行,则需要一些额外的代码来捕获它。
- 这是一个潜在的性能问题。 有一些与exception相关的开销,如果你抛出很多exception,你可能会发现应用程序性能下降。
这里有一些关于这个主题的例子和一些有趣的讨论。
免责声明:上面的代码改编自该维基页面上的第一个示例,将其转换为C#。