Danom 1.2.0

dotnet add package Danom --version 1.2.0                
NuGet\Install-Package Danom -Version 1.2.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Danom" Version="1.2.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Danom --version 1.2.0                
#r "nuget: Danom, 1.2.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Danom as a Cake Addin
#addin nuget:?package=Danom&version=1.2.0

// Install Danom as a Cake Tool
#tool nuget:?package=Danom&version=1.2.0                

Danom

NuGet Version build

Danom is a C# library that provides (monadic) structures to facilitate durable programming patterns in C# using Option and Result.

Key Features

Design Goals

  • Easy to use API for common monadic operations.
  • Efficient implementation to minimize overhead.
  • Seamless integration with existing C# code and libraries.
  • Prevent direct use of internal value, enforcing exhaustive matching.

Getting Started

Install the Danom NuGet package:

PM>  Install-Package Danom

Or using the dotnet CLI

dotnet add package Danom

Quick Start

using Danom;

// Working with Option type
var option = Option<int>.Some(5);

option.Match(
    some: x => Console.WriteLine("Value: {0}", x),
    none: () => Console.WriteLine("No value"));

// Working with Result type
public Result<int, string> TryDivide(
    int numerator,
    int denominator) =>
    denominator == 0
        ? Result<int, string>.Error("Cannot divide by zero")
        : Result<int, string>.Ok(numerator / denominator);

TryDivide(10, 2)
    .Match(
        ok: x => Console.WriteLine("Result: {0}", x),
        error: e => Console.WriteLine("Error: {0}", e));

Option

Options have an underlying type and can optionally hold a value of that type. Options are a much safer way to handle nullable values, they virtually eliminate null reference exceptions. They also provide a fantastic means of reducing primitive congestion in your code.

Creating Options

var option = Option<int>.Some(5);

// or, with type inference
var optionInferred = Option.Some(5);

// or, with no value
var optionNone = Option<int>.None();

// also returns none
var optionNull = Option<object>.Some(default!);

Using Option

Options are commonly used when a operation might not return a value. For example, the method below tries to find a number in a list that satisfies a predicate. If the number is found, it is returned as a Some, otherwise, None is returned.

public Option<int> TryFind(IEnumerable<int> numbers, Func<int, bool> predicate) =>
    numbers.FirstOrDefault(predicate).ToOption();

With this method defined we can begin performing operations against the Option result:

IEnumerable<int> nums = [1,2,3];

// Exhaustive matching
TryFind(nums, x => x == 1)
    .Match(
        some: x => Console.WriteLine("Found: {0}", x),
        none: () => Console.WriteLine("Did not find number"));

// Mapping the value
Option<int> optionSum =
    TryFind(nums, x => x == 1)
        .Map(x => x + 1);

// Binding the option
Option<int> optionBindSum =
    TryFind(nums, x => x == 1)
        .Bind(num1 =>
            TryFind(nums, x => x == 2)
                .Map(num2 => num1 + num2));

// Handling "None"
Option<int> optionDefault =
    TryFind(nums, x => x == 4)
        .DefaultValue(99);

Option<int> optionDefaultWith =
    TryFind(nums, x => x == 4)
        .DefaultWith(() => 99); // useful if creating the value is expensive

Option<int> optionOrElse =
    TryFind(nums, x => x == 4)
        .OrElse(Option<int>.Some(99));

Option<int> optionOrElseWith =
    TryFind(nums, x => x == 4)
        .OrElseWith(() => Option<int>.Some(99)); // useful if creating the value is expensive

Result

Results are used to represent a success or failure outcome. They provide a more concrete way to manage the expected errors of an operation, then throwing exceptions. Especially in recoverable or reportable scenarios.

Creating Results

var result = Result<int, string>.Ok(5);

// or, with an error
var resultError = Result<int, string>.Error("An error occurred");

// or, using the built-in Error type
var resultErrors = Result<int>.Ok(5);

var resultErrorsError = Result<int>.Error("An error occurred");
var resultErrorsMultiError = Result<int>.Error(["An error occurred", "Another error occurred"]);
var resultErrorsTyped = Result<int>.Error(new ResultErrors("error-key", "An error occurred"));

Using Results

Results are commonly used when an operation might not succeed, and you want to manage or report back the expected errors. For example:

public Result<int, string> TryDivide(int numerator, int denominator) =>
    denominator == 0
        ? Result<int, string>.Error("Cannot divide by zero")
        : Result<int, string>.Ok(numerator / denominator);

With this method defined we can begin performing operations against the Result result:

// Exhaustive matching
TryDivide(10, 2)
    .Match(
        ok: x => Console.WriteLine("Result: {0}", x),
        error: e => Console.WriteLine("Error: {0}", e));

// Mapping the value
Result<int, string> resultSum =
    TryDivide(10, 2)
        .Map(x => x + 1);

// Binding the result (i.e., when a nested operation also returns a Result)
Result<int, string> resultBindSum =
    TryDivide(10, 2)
        .Bind(num1 =>
            TryDivide(20, 2)
                .Map(num2 =>
                    num1 + num2));

// Handling errors
Result<int, string> resultDefault =
    TryDivide(10, 0)
        .DefaultValue(99);

Result<int, string> resultDefaultWith =
    TryDivide(10, 0)
        .DefaultWith(() => 99); // useful if creating the value is expensive

Result<int, string> resultOrElse =
    TryDivide(10, 0)
        .OrElse(Result<int, string>.Ok(99));

Result<int, string> resultOrElseWith =
    TryDivide(10, 0)
        .OrElseWith(() =>
            Result<int, string>.Ok(99)); // useful if creating the value is expensive

Since error messages are frequently represented as keyed string collections, the ResultErrors type is provided to simplify Result creation. The flexible constructor allows errors to be initialized with a single string, a collection of strings, or a key-value pair.

var resultErrors =
    Result<int>.Ok(5);

var resultErrorsError =
    Result<int>.Error("An error occurred");

var resultErrorsMultiError =
    Result<int>.Error(["An error occurred", "Another error occurred"]);

var resultErrorsTyped =
    Result<int>.Error(new ResultErrors("error-key", "An error occurred"));

ResultOption

Represents a combination of the Result and Option monads. This is useful when you want to handle both the success and failure of an operation, but also want to handle the case where a value might not exist. It simplifies the inspection by eliminating the redundant nested Match calls.

Creating ResultOptions

var resultOption = ResultOption<int, string>.Ok(5);

// or, with an error
var resultOptionError = ResultOption<int, string>.Error("An error occurred");

// or, with no value
var resultOptionNone = ResultOption<int, string>.None();

Using ResultOptions

ResultOptions are commonly used when an operation might not succeed, but also where a value might not exist. For example:

public Option<int> LookupUserId(string username) => // ...

public ResultOption<int, string> GetUserId(string username)
{
    if(username == "admin")
    {
        return ResultOption<int,string>.Error("Invalid username");
    }

    return LookupUserId(username).Match(
        some: id => ResultOption<int, string>.Ok(1) :
        none: ResultOption<int, string>.None);

    // or, using the extension method
    // return LookupUserId(username).ToResultOption();
}

Integrations

Since Danom introduces types that are most commonly found in your model and business logic layers, external integrations are not only inevitable but required to provide a seamless experience when build applications.

Fluent Validation Integration

Danom is integrated with Fluent Validation to provide a seamless way to validate your models and return a Result or ResultOption with the validation errors.

Documentation can be found here.

ASP.NET Core MVC Integration

Danom is integrated with ASP.NET Core to provide a set of utilities to help integrate the Danom library with common tasks in ASP.NET Core MVC applications.

Documentation can be found here.

ASP.NET Core Minimal API Integration

Coming soon

Contribute

Thank you for considering contributing to Danom, and to those who have already contributed! We appreciate (and actively resolve) PRs of all shapes and sizes.

We kindly ask that before submitting a pull request, you first submit an issue or open a discussion.

If functionality is added to the API, or changed, please kindly update the relevant document. Unit tests must also be added and/or updated before a pull request can be successfully merged.

Only pull requests which pass all build checks and comply with the general coding guidelines can be approved.

If you have any further questions, submit an issue or open a discussion.

Find a bug?

There's an issue for that.

License

Built with ♥ by Pim Brouwers in Toronto, ON. Licensed under Apache License 2.0.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Danom:

Package Downloads
Danom.Validation

Validators and validation helpers for Danom based on FluentValidation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.0 109 12/6/2024
1.1.1 91 12/6/2024
1.1.0 84 12/1/2024
1.0.0 134 11/18/2024
1.0.0-beta1 113 10/11/2024
1.0.0-alpha3 94 8/31/2024
1.0.0-alpha2 107 8/30/2024
1.0.0-alpha1 81 8/28/2024