https://github.com/barionlp/ametrin.optional
A simple C#/.NET library containing various option types
https://github.com/barionlp/ametrin.optional
csharp dotnet optionals
Last synced: about 2 months ago
JSON representation
A simple C#/.NET library containing various option types
- Host: GitHub
- URL: https://github.com/barionlp/ametrin.optional
- Owner: BarionLP
- License: mit
- Created: 2024-10-14T14:36:05.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-18T09:01:30.000Z (over 1 year ago)
- Last Synced: 2024-10-19T12:44:24.690Z (over 1 year ago)
- Topics: csharp, dotnet, optionals
- Language: C#
- Homepage:
- Size: 39.1 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 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.
```bash
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`
Represents a value of type `T` or nothing (error state).
```csharp
// Creating Options
Option b = Option.Success(someT); // explicit success creation (throws if someT is null)
Option c = Option.Error(); // explicit error creation
Option a = Option.Of(someT); // returns Option.Error() if someT is null
Option a = someT; // implicit conversion from T -> Option.Of(someT)
Option d = default; // default results in an error state -> Option.Error()
```
### `Result` and `Result`
Like `Option` but with error information.
`Result` uses an Exception as error type (easy to integrate)
`Result` uses a custom error type (recommended for performance)
```csharp
Result b = Result.Success(someT); // explicit success creation (throws if someT is null)
Result c = Result.Error(new Exception()); // explicit error creation
Result a = Result.Of(someT); // returns Result.Error(new NullReferenceException()) if someT is null
Result a = someT; // implicit conversion from T -> Result.Of(someT)
Result a = new Exception(); // implicit conversion from Exception -> Result.Error(new Exception())
// same concept for Result
```
### `ErrorState` and `ErrorState`
Represents a success state or an error value
`ErrorState` uses an Exception as error type
`ErrorState` uses a custom error type
```csharp
ErrorState success = default; // ErrorState.Success()
ErrorState error = new Exception(); // ErrorState.Error(new Exception())
// same applies for ErrorState (with generic arguments)
```
### `Option`
Represents a success state or error state. Holds no value.
```csharp
Option success = true; // Option.Success();
Option error = false; // Option.Error();
```
## Core API
```csharp
// 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
```csharp
// Combine multiple options
optionA.Join(optionB).Map((a, b) => a + b);
optionA.Join(optionB).Consume(
success: (a, b) => Console.WriteLine($"{a} + {b} = {a + b}"),
error: () => Console.WriteLine("One of the values was missing")
);
```
### Async Support
```csharp
// Async operations
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`
A reduced version of `Option` 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`:
```csharp
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:
```csharp
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](./benchy/Examples/ParsingDateTimeBenchmarks.cs):
```
| Method | Mean | Error | StdDev | Allocated |
|---------------- |---------:|---------:|---------:|----------:|
| Default_Success | 82.38 ns | 0.361 ns | 0.337 ns | - |
| Option_Success | 83.71 ns | 0.360 ns | 0.336 ns | - |
| Default_Error | 70.07 ns | 0.244 ns | 0.216 ns | - | // using TryParse
| Option_Error | 69.47 ns | 0.225 ns | 0.199 ns | - |
```