Skip to content

gnaeus/OperationResult

Repository files navigation

OperationResult

Rust-style error handling for C#

GitHub license NuGet version

using OperationResult; using static OperationResult.Helpers; public Result<double, string> SqrtOperation(double argument) { if (argument < 0) { return Error("Argument must be greater than zero"); } double result = Math.Sqrt(argument); return Ok(result); } public void Method() { var result = SqrtOperation(123); if (result) { Console.WriteLine("Value is: {0}", result.Value); } else { Console.WriteLine("Error is: {0}", result.Error); } }

Result<TResult>

Result of some method when there is no TError type defined

public struct Result<TResult> { public readonly TResult Value; public bool IsError { get; } public bool IsSuccess { get; } public static implicit operator bool(Result<TResult> result); public static implicit operator Result<TResult>(TResult result); }

Example

public Result<uint> Square(uint argument) { if (argument >= UInt16.MaxValue) { return Error(); } return Ok(argument * argument); }

Result<TResult, TError>

Either Result of some method or Error from this method

public struct Result<TResult, TError> { public readonly TError Error; public readonly TResult Value; public bool IsError { get; } public bool IsSuccess { get; } public void Deconstruct(out TResult result, out TError error); public static implicit operator bool(Result<TResult, TError> result); public static implicit operator Result<TResult, TError>(TResult result); }

Also Result has shorthand implicit conversion from TResult type

using OperationResult; using static OperationResult.Helpers; public async Task<Result<string, HttpStatusCode>> DownloadPage(string url) { using (var client = new HttpClient()) using (var response = await client.GetAsync(url)) { if (response.IsSuccessStatusCode) { // return string as Result return await response.Content.ReadAsStringAsync(); } return Error(response.StatusCode); } }

Result<TResult, TError1, TError2, ...>

Either Result of some method or multiple Errors from this method

public struct Result<TResult, TError1, TError2, ...> { public readonly TError Error; public readonly TResult Value; public bool IsError { get; } public bool IsSuccess { get; } public void Deconstruct(out TResult result, out object error); public static implicit operator bool(Result<TResult, TError1, ...> result); public static implicit operator Result<TResult, TError1, ...>(TResult result); }

Example

public Result<int, InnerError> Inner() { return Error(new InnerError()); } public Result<int, OuterError, InnerError> Outer() { var result = Inner(); if (!result) { return Error(result.Error); } return Error(new OuterError()); } public void Method() { var result = Outer(); if (result) { // ... } else if (result.HasError<InnerError>()) { Console.WriteLine("{0}", result.GetError<InnerError>()); } else if (result.HasError<OuterError>()) { Console.WriteLine("{0}", result.GetError<OuterError>()); } }

Status

Status of some operation without result when there is no TError type defined

public struct Status { public bool IsError { get; } public bool IsSuccess { get; } public static implicit operator bool(Status status); }

Example

public Status IsOdd(int value) { if (value % 2 == 1) { return Ok(); } return Error(); }

Status<TError>

Status of some operation without result

public struct Status<TError> { public readonly TError Error; public bool IsError { get; } public bool IsSuccess { get; } public static implicit operator bool(Status<TError> status); }

Example

public Status<string> Validate(string input) { if (String.IsNullOrEmpty(input)) { return Error("Input is empty"); } if (input.Length > 100) { return Error("Input is too long"); } return Ok(); }

Status<TError1, TError2, ...>

Status of some operation without result but with multiple Errors from this method

public struct Status<TError1, TError2, ...> { public readonly object Error; public bool IsError { get; } public bool IsSuccess { get; } public TError GetError<TError>(); public bool HasError<TError>(); public static implicit operator bool(Status<TError1, ...> status); }

Example

public Status<InnerError> Inner() { return Error(new InnerError()); } public Status<OuterError, InnerError> Outer() { var result = Inner(); if (!result) { return Error(result.Error); } return Error(new OuterError()); }

Helpers

public static class Helpers { public static SuccessTag Ok(); public static ErrorTag Error(); public static SuccessTag<TResult> Ok<TResult>(TResult result); public static ErrorTag<TError> Error<TError>(TError error); }

Benchmarks

A performance comparsion with other error handling techniques

Method SuccessRate Mean StdDev Gen 0 Allocated
TResult Operation() + Exception 50 1 068 491 ns 2 754.82 ns - 10.4 kB
Result<TResult, TError> Operation() 50 2 025 ns 0.67 ns - 0 B
Tuple<TResult, TError> Operation() 50 957 ns 1.71 ns 0.9669 1.6 kB
bool Operation(out TResult value, out TError error) 50 650 ns 0.15 ns - 0 B
TResult Operation() + Exception 90 212 520 ns 529.55 ns - 2.08 kB
Result<TResult, TError> Operation() 90 1 995 ns 1.86 ns - 0 B
Tuple<TResult, TError> Operation() 90 815 ns 1.18 ns 0.9669 1.6 kB
bool Operation(out TResult value, out TError error) 90 463 ns 0.41 ns - 0 B
TResult Operation() + Exception 99 22 069 ns 52.44 ns - 208 B
Result<TResult, TError> Operation() 99 1 989 ns 2.84 ns - 0 B
Tuple<TResult, TError> Operation() 99 778 ns 1.31 ns 0.9669 1.6 kB
bool Operation(out TResult value, out TError error) 99 430 ns 0.34 ns - 0 B

About

Rust-style error handling for C#

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages