Advantages of Using Result Pattern in C# .Net with ErrorOr

Introduction

Hello, devs!

Today we will talk about an approach that can transform the way you handle errors in your C# applications: the Result Pattern. By using the ErrorOr package by Amichai Mantinband, we will explore the advantages of this practice over traditional exception handling and understand why it can be up to 7 times more performant. Ready?

Let’s go!

Result Pattern

What is Result Pattern?

The Result Pattern is an approach to handle operations that can fail, returning a result that encapsulates both success and error.

Instead of throwing exceptions, the method returns an object containing the success or error information, allowing the caller to handle it explicitly.

How It Works

In the Result Pattern, a method that can fail returns an object that can be either a success or an error. In C#, this is often represented with a generic type that encapsulates both scenarios. The ErrorOr package is a popular implementation of this pattern.

public class Result<T>
{
    public T Value { get; }
    public string Error { get; }
    public bool IsSuccess { get; }

    private Result(T value, string error, bool isSuccess)
    {
        Value = value;
        Error = error;
        IsSuccess = isSuccess;
    }

    public static Result<T> Success(T value) => new Result<T>(value, null, true);
    public static Result<T> Failure(string error) => new Result<T>(default, error, false);
}

Usage Example

public Result<string> GetUserName(int userId)
{
    if (userId <= 0)
    {
        return Result<string>.Failure("Invalid user ID");
    }

    // Assume we fetch the user name from a database
    string userName = "User_" + userId;
    return Result<string>.Success(userName);
}

Advantages of Result Pattern over Exceptions

Performance

Using Result Pattern can be significantly faster than using exceptions.

As indicated by a study from Greg Young, throwing and catching exceptions in .Net can be up to 7 times slower than explicitly returning success or failure results.

Other studies indicate that in error scenarios, exceptions can be up to 10 times more costly in terms of performance compared to using result objects (MS Learn) (Youssef Sellami) (Stack Overflow).

Benchmark Result Pattern

Explicit Control

With the Result Pattern, the control flow is more explicit. The developer is forced to handle both scenarios (success and failure), which can lead to more robust and understandable code.

Easier Debugging

Logic errors are easier to identify and fix with the Result Pattern, as failure points are more obvious. With exceptions, it can be difficult to trace where and why an error occurred.

Less System Overhead

Exceptions in .Net are expensive in terms of performance. They consume more memory and processing, which can negatively impact the performance of high-load applications.

Performance Comparison

Let’s take a look at how using the Result Pattern can be more efficient than exceptions. Based on Greg Young’s article, throwing exceptions can add significant overhead to an application’s runtime.

Performance Test

Let’s run a simple test to illustrate the difference:

public Result<int> Divide(int numerator, int denominator)
{
    if (denominator == 0)
    {
        return Result<int>.Failure("Division by zero");
    }

    return Result<int>.Success(numerator / denominator);
}

public int DivideWithException(int numerator, int denominator)
{
    if (denominator == 0)
    {
        throw new DivideByZeroException();
    }

    return numerator / denominator;
}

Benchmark Results

In tests conducted with millions of division operations, the DivideWithException method showed significantly higher time overhead.

On average, using exceptions was approximately 10 times slower than using the Result Pattern in error scenarios, and 3 times faster in success scenarios due to the creation of the result object (MS Learn) (Youssef Sellami).

This confirms the performance advantage of the Result Pattern, especially in high-load scenarios where efficiency is critical.

Practical Implementation with ErrorOr

Now, let’s see how to implement the Result Pattern using the ErrorOr package in a Web API. This package provides a convenient and efficient way to work with the Result Pattern in C#.

Project Setup

First, add the ErrorOr package to your project:

dotnet add package ErrorOr

Example in a Web API

Let’s look at a complete example of a Web API that uses ErrorOr for error handling and returns Task<IActionResult>.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ErrorOr;

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    private readonly UserService _userService;

    public UsersController(UserService userService)
    {
        _userService = userService;
    }

    [HttpGet("{userId}")]
    public async Task<IActionResult> GetUserName(int userId)
    {
        var result = await _userService.GetUserNameAsync(userId);

        return result.Match(
            value => Ok(value),
            errors => BadRequest($"{errors.Count} errors occurred.")
        );
    }
}

public class UserService
{
    public async Task<ErrorOr<string>> GetUserNameAsync(int userId)
    {
        if (userId <= 0)
        {
            return ErrorOr.Error("Invalid user ID");
        }

        // Simulating fetching the user's name
        string userName = "User_" + userId;
        return await Task.FromResult(ErrorOr.Success(userName));
    }
}

Credits

The ErrorOr package was created by Amichai Mantinband. You can check out the repository on GitHub here.

Final Considerations

Adopting the Result Pattern in your C# applications can bring several advantages in terms of performance, control, and code maintenance. Using the ErrorOr package makes implementing this approach easier, resulting in more robust and debuggable code. If you haven’t tried this technique yet, it’s worth considering for your next project!

Did you enjoy the tips? Share your experiences and questions in the comments

See ya!