---
title: Why I Love Control Flow in C#
theme: default
layout: center
class: text-center
fonts:
sans: "CaskaydiaCove Nerd Font Mono"
---
# Why I love control flow in C#
---
layout: center
class: text-center
---
# The false dichotomy
Team "Never Throw"
"Exceptions are expensive! Treat errors as values! Not every failure is exceptional!"
Team "Always Throw"
"Results are noisy! Just throw and let the middleware handle it! Keep the code clean!"
> Why not both?
---
layout: center
---
# The sweet spot
Global Handler
Prevent crashes
Controller
Matches result
Service Layer
Returns results
Deep / Domain
Throws exceptions
---
layout: center
---
# An example in a web API
---
layout: center
---
# Use exceptions for context agnostic code
```csharp
public async Task GetUserAsync(int userId)
{
// NOTE: EF Core might throw exceptions for connection issues, etc.
var user = await _dbContext.Users.FindAsync(userId);
// NOTE: Domain logic might throw exceptions for business rules
if (user?.IsBanned)
{
throw new UserBannedException(userId);
}
return user;
}
```
---
# Service layer returns results
```csharp
public async Task> GetUserAsync(int userId)
{
try
{
var user = await _userService.GetUserAsync(userId);
if (user is null)
{
return Result.Failure(new UserNotFoundException(userId));
}
return Result.Success(user);
}
catch (UserBannedException ex)
{
return Result.Failure(ex);
}
}
```
---
# Match results in the controller
```csharp
public static async Task GetUserHandler(int userId, IUserService _service)
{
var result = await _service.GetUserAsync(userId);
return result.Match(
user => Ok(user),
error => error switch
{
UserNotFoundException => Results.NotFound(),
UserBannedException => Results.Forbid(),
_ => Results.InternalServerError()
}
);
}
```
---
# Global handler for unexpected errors
```csharp
public async ValueTask TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken
)
{
_logger.LogError(
exception,
"Exception occurred: {Message}", exception.Message
);
httpContext.Response.StatusCode =
StatusCodes.Status500InternalServerError;
await httpContext.Response
.WriteAsync("An unexpected error occurred.", cancellationToken);
return true;
}
```
---
layout: center
---
# C# gives me the freedom to choose
- Use exceptions when deep in the call stack.
- Use result types where business logic is centralized.
- Use global handlers for unexpected exceptions and to prevent crashes.