我正在考虑使用回调而不是在C#/ .NET中抛出异常。
优点是
缺点是
我可能缺少一些关键的缺点,因为我想知道为什么不使用它。我错过了什么缺点?
而不是
void ThrowingMethod() {
throw new Exception();
}
和
void CatchingMethod() {
try {
ThrowingMethod();
} catch(Exception e) {
//handle exception
}
}
我愿意
void ThrowingMethod(ExceptionHandler exceptionHandler) {
exceptionHandler.handle(new Exception());
}
void CatchingMethod() {
ThrowingMethod(exception => */ handle exception */ );
}
带
delegate void ExceptionHandler(Exception exception);
在某处定义并且“handle(...)”是一个检查null的扩展方法,检索堆栈跟踪,如果在抛出异常时根本没有异常处理程序,则可能抛出“UnhandledException”。 p>
void UsedToNotThrowButNowThrowing() {
UsedToNotThrowButNowThrowing(null);
}
//overloads existing method that did not throw to now throw
void UsedToNotThrowButNowThrowing(ExceptionHandler exceptionHandler) {
//extension method "handle" throws an UnhandledException if the handler is null
exceptionHandler.handle(exceptionHandler);
}
TResult ThrowingMethod(ExceptionHandler<TResult> exceptionHandler) {
//code before exception
return exceptionHandler.handle(new Exception()); //return to interrupt execution
//code after exception
}
TResult CatchingMethod() {
return ThrowingMethod(exception => */ handle exception and return value */ );
}
带
delegate TResult ExceptionHandler<TResult>(Exception exception);
答案 0 :(得分:0)
首先,您需要将这些处理程序的开销传递给应用程序中的几乎所有方法。这是一个非常重量级的依赖,以及在构建应用程序之前做出的决定。
其次,存在处理系统抛出异常和第三方程序集的其他异常的问题。
第三,异常是指在抛出程序时暂停程序的执行,因为它确实是“异常”,而不仅仅是可以处理的错误,允许继续执行。
答案 1 :(得分:0)
<强>可扩展性。强>
正如@mungflesh正确地指出你必须传递这些处理程序。我的第一个问题不是开销,而是可伸缩性:它会影响方法签名。它可能会导致与我们在Java中使用已检查异常相同的可伸缩性问题(我不了解C#,我只做C ++和一些Java)。
想象一下一个深度为50次调用的调用堆栈(没有什么极端的,IMO)。有一天,一个变化出现了,并且没有抛出的链中的一个被调用者变成了一个现在可以抛出异常的方法。如果它是未经检查的异常,您只需更改顶级代码即可处理新错误。如果它是已检查的异常或您应用了您的想法,则必须通过调用链更改所有涉及的方法签名。不要忘记签名更改会传播:您更改这些方法的签名,您必须在调用这些方法的其他地方更改代码,可能会生成更多签名更改。简而言之,规模很差。 :(
这是一些伪代码,显示了我的意思。使用未经检查的异常,您可以通过以下方式处理深度为50的callstack中的更改:
f1() {
try { // <-- This try-catch block is the only change you have to make
f2();
}
catch(...) {
// do something with the error
}
}
f2() { // None of the f2(), f3(), ..., f49() has to be changed
f3();
}
...
f49() {
f50();
}
f50() {
throw SomeNewException; // it was not here before
}
使用您的方法处理相同的更改:
f1() {
ExceptionHandler h;
f2(h);
}
f2(ExceptionHandler h) { // Signature change
f3(h); // Calling site change
}
...
f49(ExceptionHandler h) { // Signature change
f50(h); // Calling site change
}
f50(ExceptionHandler h) {
h.SomeNewException(); // it was not here before
}
所涉及的所有方法(f2 ... f49)现在都有一个新的签名,并且呼叫站点也必须更新(例如f2()变为f2(h)等) 。请注意,f2...f49
甚至不应该知道此更改,但是,他们的签名和调用网站都必须更改。
换句话说:所有中间调用现在必须处理错误处理程序,即使它是一个他们甚至不知道的细节。使用未经检查的例外情况,可以隐藏这些详细信息。
未经检查的异常确实&#34;隐藏了如控制流&#34;但至少他们的规模很好。毫无疑问,很快就会导致难以维持的混乱...
+1虽然,这是一个有趣的想法。
答案 2 :(得分:0)
如果我做对了,如果一个方法中有两个可能的异常,则该方法需要采用两个不同的参数。为了模拟检查的异常并使通知者可能的异常,必须为不同类型的可能的异常传递不同的处理程序。因此,在多态情况下,当您定义接口或抽象类时,您将可能的异常强加给未编写的代码,因此不允许具体实现生成新类型的异常。
作为示例,您正在实现Stream类和FileStream具体类。您必须为文件未找到异常传递异常处理程序,这是不好的,因为它迫使MemoryStream接受文件未找到异常处理程序,或者,另一方面,您不允许在其中生成文件未找到异常。 FileStream,因为签名不允许这样做。