Idea
I am thinking of using callbacks instead of throwing exceptions in C# / .NET.
Pros and Cons
The advantages are
- no hidden goto like control flow of unchecked exceptions
- much cleaner code, especially if more than one exception is involved
- thrown exceptions are documented in the method signature and the caller is forced to think about handling exceptions but can easily pass an application-wide exception handler, an "UnhandledExceptionHandler", or null to them. So they are kind of like "soft" checked exceptions but more maintainable because exceptions can thrown afterwards by overloading the method or exceptions can be removed by no longer calling "handle" on the exception handler).
- works also well for asynchronous calls
- an exception handler can handle several exceptions that are thrown at different places
- makes explicit which exceptions should be handled. Throwing exceptions the ordinary way could still be used for exceptions you don't want to be handled like "NotImplementedException".
The disadvantages are
- not idiomatic to C# and .NET
- throwing method has to interrupt the control flow by immediately returning a return value. This is difficult if the return type is a value type.
- ? (see question below)
Question
I am probably missing a few critical disadvantages, because I am wondering why this is not used. What disadvantages have I missed?
Example:
Instead of
void ThrowingMethod() {
throw new Exception();
}
and
void CatchingMethod() {
try {
ThrowingMethod();
} catch(Exception e) {
//handle exception
}
}
I would do
void ThrowingMethod(ExceptionHandler exceptionHandler) {
exceptionHandler.handle(new Exception());
}
void CatchingMethod() {
ThrowingMethod(exception => */ handle exception */ );
}
with
delegate void ExceptionHandler(Exception exception);
defined somewhere and "handle(...)" being an extension method that checks for null, retrieves the stack traces and possibly throws an "UnhandledException" if there is no exception handler at all when the exception is thrown.
Example of throwing an exception in a method that did not throw an exception before
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);
}
Example with methods that return values
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 */ );
}
with
delegate TResult ExceptionHandler<TResult>(Exception exception);