Xpandables.Optionals 10.0.2

dotnet add package Xpandables.Optionals --version 10.0.2
                    
NuGet\Install-Package Xpandables.Optionals -Version 10.0.2
                    
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="Xpandables.Optionals" Version="10.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Xpandables.Optionals" Version="10.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Xpandables.Optionals" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Xpandables.Optionals --version 10.0.2
                    
#r "nuget: Xpandables.Optionals, 10.0.2"
                    
#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.
#:package Xpandables.Optionals@10.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Xpandables.Optionals&version=10.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Xpandables.Optionals&version=10.0.2
                    
Install as a Cake Tool

System.Optionals

NuGet NuGet Downloads .NET License

Optional monad for .NET — eliminate null-reference errors with a functional approach.

📖 Overview

System.Optionals (NuGet: Xpandables.Optionals) provides a lightweight Optional<T> type that represents a value that may or may not be present. It supports functional-style operations (Map, Bind, Match), comparison, JSON serialization, LINQ integration, and operator overloads. Namespace: System.Optionals.

Built for .NET 10 and C# 14. No external dependencies.

✨ Features

Type File Description
Optional<T> Optional.cs Read-only record struct — Value, IsEmpty, IsNotEmpty, implements IEnumerable<T>
Optional OptionalFactory.cs Static factory — Empty<T>(), Some<T>(value)
OptionalSync (partial) OptionalSync.cs Sync operations — Map, Bind, Match, Filter, ToOptional<TU>
OptionalAsync (partial) OptionalAsync.cs Async operations — MapAsync, BindAsync, MatchAsync, FilterAsync
OptionalOperators (partial) OptionalOperators.cs Comparison operators (<, <=, >, >=) and implicit conversions
OptionalComparer (partial) OptionalComparer.cs IComparable<Optional<T>> implementation
OptionalExtensions OptionalExtensions.cs Extension methods for converting values to Optional<T>
EnumerableExtensions EnumerableExtensions.cs LINQ helpers for IEnumerable<Optional<T>>
OptionalJsonConverterFactory OptionalJsonConverterFactory.cs System.Text.Json converter factory
OptionalJsonSerialization OptionalJsonSerialization.cs JSON serialization support

📦 Installation

dotnet add package Xpandables.Optionals

🚀 Quick Start

Creating Optionals

using System.Optionals;

// From factory
Optional<int> some = Optional.Some(42);
Optional<int> empty = Optional.Empty<int>();

// Check state
if (some.IsNotEmpty) Console.WriteLine(some.Value); // 42
if (empty.IsEmpty) Console.WriteLine("No value");

// From nullable values via extension method
string? maybeName = GetNameOrNull();
Optional<string> optName = maybeName.ToOptional();

Safe Value Access

// GetValueOrDefault with fallback value
int value = Optional.Some(42).GetValueOrDefault(0);            // 42
int fallback = Optional.Empty<int>().GetValueOrDefault(0);     // 0

// GetValueOrDefault with factory
int computed = Optional.Empty<int>().GetValueOrDefault(() => ComputeDefault()); // ComputeDefault()

// TryGetValue pattern
if (optionalUser.TryGetValue(out User user))
{
    Console.WriteLine(user.Name);
}

Map — Transform If Present

// Map with transform function
Optional<string> upper = Optional.Some("hello")
    .Map(s => s.ToUpper()); // Optional<string> "HELLO"

// Map does nothing if empty
Optional<string> noop = Optional.Empty<string>()
    .Map(s => s.ToUpper()); // Empty — no exception

// Map with side effect (action)
Optional<Order> order = Optional.Some(myOrder)
    .Map(o => Console.WriteLine($"Processing order {o.Id}"));

Bind — Transform and Flatten

// Bind to a different type
Optional<UserProfile> profile = Optional.Some(userId)
    .Bind(id => FindUserById(id))         // Optional<User>
    .Bind(user => user.Profile);          // Optional<UserProfile>

// Bind to Optional (flat map)
Optional<string> email = Optional.Some(userId)
    .Bind<User>(id => userRepository.FindById(id) is User u
        ? Optional.Some(u)
        : Optional.Empty<User>())
    .Bind(user => Optional.Some(user.Email));

Empty — Provide Fallback When Missing

// Provide default when empty
Optional<Config> config = Optional.Empty<Config>()
    .Empty(() => Config.Default);  // returns Optional.Some(Config.Default)

// Chain: try primary, then fallback
Optional<User> user = FindUserInCache(userId)
    .Empty(() => FindUserInDb(userId));

ToOptional — Type Conversion

// Convert between types
Optional<object> boxed = Optional.Some<object>("hello");
Optional<string> unboxed = boxed.ToOptional<string>(); // Optional<string> "hello"

// If types don't match, returns Empty
Optional<int> wrong = boxed.ToOptional<int>(); // Empty

Async Operations

// MapAsync
Optional<UserDto> dto = await Optional.Some(userId)
    .MapAsync(async id =>
    {
        User user = await userService.GetByIdAsync(id);
        return userId;   // stays as the same type
    });

// BindAsync — async flat map to different type
Optional<UserDto> userDto = await Optional.Some(userId)
    .BindAsync(async id =>
    {
        User? user = await db.Users.FindAsync(id);
        return user is not null
            ? Optional.Some(new UserDto(user.Id, user.Name))
            : Optional.Empty<UserDto>();
    });

// EmptyAsync — async fallback
Optional<Config> config = await Optional.Empty<Config>()
    .EmptyAsync(async () => await LoadConfigFromRemoteAsync());

Chained Async Pipeline

Optional<OrderConfirmation> confirmation = await Optional.Some(orderId)
    .BindAsync(async id => await orderService.FindAsync(id) is Order o
        ? Optional.Some(o)
        : Optional.Empty<Order>())
    .MapAsync(async order =>
    {
        await paymentService.ChargeAsync(order.CustomerId, order.Total);
        return order;
    })
    .BindAsync(async order =>
    {
        var result = await fulfillmentService.ConfirmAsync(order.Id);
        return result is not null
            ? Optional.Some(result)
            : Optional.Empty<OrderConfirmation>();
    });

if (confirmation.IsNotEmpty)
    Console.WriteLine($"Order confirmed: {confirmation.Value.ConfirmationNumber}");
else
    Console.WriteLine("Order could not be fulfilled");

LINQ Integration

// Optional<T> implements IEnumerable<T>
foreach (string item in Optional.Some("hello"))
{
    Console.WriteLine(item); // "hello"
}

// Empty optional yields nothing
foreach (string item in Optional.Empty<string>()) { /* never reached */ }

// Where — filter optional value
Optional<int> positive = Optional.Some(42).Where(x => x > 0);      // Some(42)
Optional<int> none = Optional.Some(-1).Where(x => x > 0);           // Empty

// Select / SelectMany (LINQ query syntax)
Optional<string> result =
    from user in Optional.Some(new User("Alice", "alice@example.com"))
    from profile in Optional.Some(new Profile("Developer"))
    select $"{user.Name} — {profile.Title}";
// result = Optional<string> "Alice — Developer"

// Async Select
Optional<UserDto> userDto = await Optional.Some(userId)
    .SelectAsync(async id => await FetchUserDtoAsync(id));

Collection Helpers

// FirstOrEmpty — safe first element
Optional<Product> first = products.FirstOrEmpty();
Optional<Product> match = products.FirstOrEmpty(p => p.Price > 100);

// WhereSome — extract values from a collection of optionals
IEnumerable<Optional<User>> optionalUsers = ids.Select(id => FindUser(id));
IEnumerable<User> validUsers = optionalUsers.WhereSome(); // only non-empty values

Comparison and Equality

// Optional<T> supports comparison when T : IComparable<T>
Optional<int> a = Optional.Some(10);
Optional<int> b = Optional.Some(20);

bool less = a < b;     // true
bool greater = a > b;  // false

// Equality
bool equal = Optional.Some(42) == Optional.Some(42); // true
bool notEqual = Optional.Some(42) == Optional.Empty<int>(); // false

JSON Serialization

using System.Text.Json;

// Optional<T> serializes as the value itself (or null when empty)
string json = JsonSerializer.Serialize(Optional.Some(42));    // "42"
string emptyJson = JsonSerializer.Serialize(Optional.Empty<int>()); // "null"

// Deserialization
Optional<int> deserialized = JsonSerializer.Deserialize<Optional<int>>("42");
// deserialized.Value == 42

Real-World Example: Repository Pattern

public interface IUserRepository
{
    Optional<User> FindById(Guid id);
    Task<Optional<User>> FindByEmailAsync(string email, CancellationToken ct);
}

public class UserAppService(IUserRepository repository)
{
    public async Task<string> GetGreetingAsync(string email, CancellationToken ct)
    {
        return (await repository.FindByEmailAsync(email, ct))
            .Bind(user => Optional.Some($"Hello, {user.Name}!"))
            .GetValueOrDefault("User not found");
    }
}

📁 Project Structure

System.Optionals/
├── Optional.cs                    # Core Optional<T> record struct
├── OptionalFactory.cs             # Static factory (Empty, Some)
├── OptionalSync.cs                # Map, Bind, Match, Filter
├── OptionalAsync.cs               # MapAsync, BindAsync, MatchAsync
├── OptionalOperators.cs           # Comparison & conversion operators
├── OptionalComparer.cs            # IComparable implementation
├── OptionalExtensions.cs          # Extension methods
├── EnumerableExtensions.cs        # LINQ helpers
├── OptionalJsonConverterFactory.cs
└── OptionalJsonSerialization.cs

📄 License

Apache License 2.0 — Copyright © Kamersoft 2025

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

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.2 154 3/15/2026
10.0.1 120 2/20/2026
10.0.0 128 1/9/2026