C
C#6mo ago
kask

✅ Reducing exception usage, async methods

I've read a lot of using exceptions, best practices and such. As quick summary, the exceptions should be used on unexpected situations only (config errors, db connection errors..), not as validation errors and such. I understood, that for example, byte[] GetFileBytes(string filePath) shouldn't throw if file doesn't exist. I'm refactoring some my old projects as practice. For synchronous methods I've started using some TryGet pattern and it works great. But what about async methods? Async methods can't have out parameters. If we look at my earlier example and convert it into async method async Task<byte[]> GetFileBytes(string filePath), should I convert it to synchronous method or use some other pattern? Or should I throw a custom exception and attach original exception as inner? Catching all exceptions in multiple services and methods doesn't seem to be efficient ( catch (Exception e) when (e is PathTooLongException or DirectoryNotFoundException or IOException or UnauthorizedAccessException or FileNotFoundException or SecurityException) )
6 Replies
Angius
Angius6mo ago
You could return a tuple or some other Result sort of a pattern Or return null for errors Definitely don't convert async methods into sync, they're async for a reason
kask
kaskOP6mo ago
Thanks, I'll read about result patterns.
Angius
Angius6mo ago
Basically, in case of GetFileBytes() for example, you'd do it like so:
public enum FileBytesError {
NotFound,
ReadError,
NoAccess,
}

public sealed record FileBytesResult
{
public static FileBytesResult Success(byte[] bytes)
=> new { Bytes = bytes, Success = true };

public static FileBytesResult Error(FileBytesError Error)
=> new { Error = error, Success = false };

[MemberNotNullWhen(returnValue: true, nameof(Success))]
public byte[]? Bytes { get; private set; }

[MemberNotNullWhen(returnValue: false, nameof(Success))]
public FileBytesError Error? { get; private set; }

public bool Success { get; private set; }
}
public enum FileBytesError {
NotFound,
ReadError,
NoAccess,
}

public sealed record FileBytesResult
{
public static FileBytesResult Success(byte[] bytes)
=> new { Bytes = bytes, Success = true };

public static FileBytesResult Error(FileBytesError Error)
=> new { Error = error, Success = false };

[MemberNotNullWhen(returnValue: true, nameof(Success))]
public byte[]? Bytes { get; private set; }

[MemberNotNullWhen(returnValue: false, nameof(Success))]
public FileBytesError Error? { get; private set; }

public bool Success { get; private set; }
}
Your method would then be
public FileBytesResult GetFileBytes()
{
// try reading the file, if it worked return
FileBytesResult.Success(file);
// if something bad happened, return
FileBytesResult.Error(FileFileBytesError.NotFound);
// etc
}
public FileBytesResult GetFileBytes()
{
// try reading the file, if it worked return
FileBytesResult.Success(file);
// if something bad happened, return
FileBytesResult.Error(FileFileBytesError.NotFound);
// etc
}
kask
kaskOP6mo ago
Converted several services to use this pattern. I like it, thanks!
Unknown User
Unknown User6mo ago
Message Not Public
Sign In & Join Server To View
MODiX
MODiX6mo ago
If you have no further questions, please use /close to mark the forum thread as answered

Did you find this page helpful?