Skip to content

BarionLP/Ametrin.Optional

Repository files navigation

Ametrin.Optional

A modern, allocation-free library providing robust optional types for .NET, offering a flexible and efficient way to handle nullable values, errors, and exceptions with a fluent, monadic API.

Options vs. Exceptions

Exceptions are relatively expensive and are best reserved for unexpected failures and programmer mistakes.
For expected errors (like invalid user input or parse failures), it's often better to return a lightweight value the caller must handle.
This makes error handling explicit, cheap, and hard to ignore without using exceptions for normal control flow.

dotnet add package Ametrin.Optional

Features

  • Maximum Performance - A zero cost abstraction (mostly)
  • Monadic API - Fluent interface for transformations and error handling of all kind
  • Integration - Seamless integration with existing C# code
  • Variety - Different types for various use cases
  • Async Support - First-class support for async operations

Core Types

Option<T>

Represents a value of type T or nothing (error state).

// Creating Options
Option<T> b = Option.Success(someT);    // explicit success creation (throws if someT is null)
Option<T> c = Option.Error<T>();        // explicit error creation
Option<T> a = Option.Of(someT);         // returns Option.Error<T>() if someT is null
Option<T> a = someT;                    // implicit conversion from T -> Option.Of(someT)
Option<T> d = default;                  // default results in an error state -> Option.Error<T>() 

Result<T> and Result<T, E>

Like Option<T> but with error information.
Result<T> uses an Exception as error type (easy to integrate) Result<T, E> uses a custom error type (recommended for performance)

Result<T> b = Result.Success(someT);            // explicit success creation (throws if someT is null)
Result<T> c = Result.Error<T>(new Exception()); // explicit error creation
Result<T> a = Result.Of(someT);                 // returns Result.Error<T>(new NullReferenceException()) if someT is null
Result<T> a = someT;                            // implicit conversion from T -> Result.Of(someT)
Result<T> a = new Exception();                  // implicit conversion from Exception -> Result.Error<T>(new Exception())
// same concept for Result<T, E>

ErrorState and ErrorState<E>

Represents a success state or an error value
ErrorState uses an Exception as error type ErrorState<E> uses a custom error type

ErrorState success = default;           // ErrorState.Success()
ErrorState error = new Exception();     // ErrorState.Error(new Exception())
// same applies for ErrorState<E> (with generic arguments)

Option

Represents a success state or error state. Holds no value.

Option success = true;          // Option.Success();
Option error = false;           // Option.Error();

Core API

// common operations
option.Map(value => value * 2);         // transform value if present
option.Require(value => value > 0);     // filter based on predicate
option.Reject(value => value > 0);      // inverse of Require
option.Or(defaultValue);                // return value or defaultValue
option.OrThrow();                       // return value or throw if error
option.Match(                           // reduce to a value (equivalent to .Map(success).Or(error) but supports ref structs)
    success: value => value, 
    error: error => defaultValue
);
option.Consume(                         // handle success and error
    success: value => { }, 
    error: error => { }
);
// all operations have an overload that allows you to pass an argument into the delegate an avoid the closure

Advanced Features

Tuple Operations

// Combine multiple options
(optionA, optionB).Map((a, b) => a + b);
(optionA, optionB).Consume(
    success: (a, b) => Console.WriteLine($"{a} + {b} = {a + b}"),
    error: () => Console.WriteLine("One of the values was missing")
);

Async Support

// Async operations with full option support
var text = await new FileInfo("file.txt")
    .RequireExists()
    .MapAsync(f => File.ReadAllTextAsync(f.FullName))
    .MapAsync(s => s.ToLower());

await text.ConsumeAsync(text => File.WriteAllTextAsync("output.txt", text));

RefOption<T>

A reduced version of Option<T> that can hold a ref struct as value

Edge Cases

For edge cases, high-performance scenarios where delegates cannot be static or when you absolutly need to modify the control flow use Branch:

if(result.Branch(out var value, out var error))
{
    // success
}
else
{
    // error
}

If there is no way around it you can get low-level access to all option types through OptionsMarshall.

Testing

For testing code using Ametrin.Optional types, use the Ametrin.Optional.Testing.TUnit extensions:

await Assert.That(option).IsSuccess(expectedValue);
await Assert.That(option).IsError();

Contributing

Contributions are welcome! Feel free to:

  • Create issues for bugs or feature requests
  • Submit pull requests (discuss design first)
  • Add extensions for more testing frameworks

Performance

The library is designed with performance in mind and has minimal to no overhead thanks to the jit.

Example benchmark for parsing a DateTime:

| Method          | Mean     | Error    | StdDev   | Allocated |
|---------------- |---------:|---------:|---------:|----------:|
| Default_Success | 87.31 ns | 1.696 ns | 1.666 ns |         - |
| Option_Success  | 89.95 ns | 1.821 ns | 2.492 ns |         - |
| Default_Error   | 73.27 ns | 1.462 ns | 2.361 ns |         - | // using TryParse
| Option_Error    | 74.05 ns | 1.464 ns | 1.504 ns |         - |

About

A simple and fast .NET library containing various functional option types

Topics

Resources

License

Stars

Watchers

Forks