C#'s primary error handling mechanism are exceptions and try pattern.
We don't have access to discriminated unions yet like in case of F# and Rust Option<T>
and Result<T, E>
types.
The official Microsoft guidelines mention that exceptions should be avoided for errors that can happen often.
The try pattern works only for non async methods. So it's not really suitable as most methods in business logic code do some I/O operations and need to be async.
For a single point of failure returning null
might make sense but in many cases there is more than 1 point of failure.
Pros of exceptions
- Can be handled globally in a single place
- The code doesn't need to check error status all the time and most importantly you don't forget to check it (unless you manage to make compile-time checks somehow)
Cons of exceptions
- Performance overhead
- You don't know what exceptions certain method throws if it's not documented and without looking at the implementation (not explicit)
Considerations and code samples
Imagine a method named AcceptPayment
which can have more than one points of failure e.g. InsufficientFunds
, UserAccountBlocked
etc.
What would be the best way to implement custom exceptions for such cases? There are 2 perhaps more different ways
1. Exception per method with Error enum
public enum AcceptPaymentError
{
InsufficientFunds,
UserAccountBlocked
}
public class AcceptPaymentException : BusinessLogicException
{
public AcceptPaymentException(AcceptPaymentError error) : base(...)
{
Error = error;
}
public AcceptPaymentError Error { get; }
}
or just using pure string instead of enum... but then there's no way easily check what the problem was.
2 Exception per point of failure
In this case we will have InsufficientFundsException
and UserAccountBlockedException
deriving directly from BusinessLogicException
so it can be used in more than 1 method.
Avoiding exceptions completely and using something temporarily in place of official discriminated union support
This is something I see many developers do nowadays. They don't use exceptions at all in their core/business logic layer but rely on custom types.
We can either use record
and switch
expressions or rely on some open source libraries providing partial support for DUs e.g.
- https://github.com/mcintyre321/OneOf
- https://github.com/Resultful/Resultful
- https://github.com/gnaeus/OperationResult
- https://github.com/altmann/FluentResults
What is your view on this subject? Would you use something like OneOf
in place of exceptions when starting a new project? Why?
https://mattwarren.org/2016/12/20/Why-Exceptions-should-be-Exceptional/
async Task<bool> TryDoSomething()
?out
parameter. And you can't haveasync
method without
orref
parameters. stackoverflow.com/questions/18716928/…maybe<T>
" by having the return beT?
wherenull
indicatesnone
. Or there are plenty of 3rd party libraries that can provide you withMaybe<T>
/Option<T>
. So you can use these for async viaTask<T?>
orTask<Maybe<T>>
You don't know what exceptions...
It doesn't matter. All exceptions in C# derive from theException
class; catch that, if you're worried about rogue exceptions.