CSharpEssentials.Maybe 6.5.2

dotnet add package CSharpEssentials.Maybe --version 6.5.2
                    
NuGet\Install-Package CSharpEssentials.Maybe -Version 6.5.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="CSharpEssentials.Maybe" Version="6.5.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CSharpEssentials.Maybe" Version="6.5.2" />
                    
Directory.Packages.props
<PackageReference Include="CSharpEssentials.Maybe" />
                    
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 CSharpEssentials.Maybe --version 6.5.2
                    
#r "nuget: CSharpEssentials.Maybe, 6.5.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 CSharpEssentials.Maybe@6.5.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=CSharpEssentials.Maybe&version=6.5.2
                    
Install as a Cake Addin
#tool nuget:?package=CSharpEssentials.Maybe&version=6.5.2
                    
Install as a Cake Tool

CSharpEssentials.Maybe

Maybe (Option) monad for null-safe, expressive code. Avoid NullReferenceException by making missing values explicit.

Features

  • Maybe<T>: Explicit presence/absence of a value with implicit conversions.
  • Functional API: Map, Bind, Where, Match, Or, Tap.
  • LINQ Support: Query syntax with Select, SelectMany, Where.
  • Async Support: MapAsync, BindAsync, and awaitable extensions.
  • Result Bridge: Convert Maybe<T> to Result<T> and back.
  • Sequence Helpers: TryFirst, TryLast, TryFind, Choose, Sequence, Traverse, Partition.

Targets net11.0, net10.0, net9.0 and netstandard2.1. Depends on CSharpEssentials.Results and CSharpEssentials.Errors. Maybe<T> and the non-generic Maybe (for Maybe.None, Maybe.From, Maybe.FromAsync and Maybe.FromTry) live in the CSharpEssentials.Maybe namespace.

Installation

dotnet add package CSharpEssentials.Maybe

Usage

Creating Maybe

using CSharpEssentials.Maybe;

public Maybe<User> FindUser(int id)
{
    User? user = _repo.Find(id);
    return user; // implicit from T? to Maybe<T>
}

Maybe<string> none = Maybe.None;
Maybe<int> value = 42;

Maybe<string> name = Maybe.From("Ann");                       // the non-generic Maybe infers T
Maybe<int> five = 5.AsMaybe();
Maybe<int> parsed = Maybe.FromTry(() => int.Parse("42"));
Maybe<string> lazy = Maybe.From(() => Environment.GetEnvironmentVariable("HOME"));

From gives None for null. Maybe<T> serializes with System.Text.Json as {"HasValue":true,"value":5} and deserializes back from that shape.

Functional Chaining

string city = FindUser(1)
    .Where(u => u.IsActive)
    .Map(u => u.Address)
    .Map(a => a.City)
    .GetValueOrDefault("Unknown");

LINQ Queries

Maybe<string> tracking =
    from user in FindUser(1)
    from order in FindOrder(user.Id)
    where order.Amount > 100
    select order.TrackingNumber;

Matching

FindUser(1).Match(
    some: user => Console.WriteLine($"Found: {user.Name}"),
    none: () => Console.WriteLine("Not found"));

string label = FindUser(1).Match(some: u => u.Name, none: () => "stranger");

string loaded = await FindUser(1).MatchAsync(
    some: async (u, ct) => await LoadProfileAsync(u, ct),   // async handlers receive a CancellationToken
    none: async ct => await LoadAnonymousAsync(ct),
    cancellationToken: token);

Reading the Value

Maybe<int> maybe = 5;

if (maybe.HasValue) { int v = maybe.Value; }          // Value throws InvalidOperationException on None
bool found = maybe.TryGetValue(out int value);
var (hasValue, inner) = maybe;                          // Deconstruct

int a = maybe.GetValueOrDefault(0);                    // fallback value
int b = maybe.GetValueOrDefault(() => 0);              // lazy fallback
int c = maybe.GetValueOrDefault(v => v * 2, -1);       // map when Some, else the fallback
int d = maybe.GetValueOrThrow("missing");              // or GetValueOrThrow(new MyException())
int? e = maybe.AsNullable();                           // T? for reference types; default(T) for value types
List<int> list = maybe.ToList();                       // one element, or empty

Filtering, Fallbacks and Side Effects

Maybe<int> big = maybe.Where(v => v > 3);               // None when the predicate fails
Maybe<int> alt = Maybe<int>.None.Or(7);                 // Or(Maybe<T>) (a plain T converts), Or(Func<T>), Or(Func<Maybe<T>>)

maybe.Tap(v => Console.WriteLine(v));                   // only when Some; returns the same Maybe
maybe.TapIf(v => v > 3, v => Console.WriteLine(v));     // predicate-gated tap
maybe.Execute(v => Console.WriteLine(v));               // Action<T> on Some
maybe.ExecuteNoValue(() => Console.WriteLine("none"));  // Action on None

Maybe<int> doubled = maybe.MapIf(v => v > 3, v => v * 2);
Maybe<int> kept = maybe.BindIf(v => v > 3, v => Maybe<int>.From(v + 1));

MapIf, BindIf and TapIf also take a bool or a Func<bool> condition. MapIfAsync and BindIfAsync exist on Maybe<T>, Task<Maybe<T>> and ValueTask<Maybe<T>>; TapIfAsync exists on the Task and ValueTask sources.

Converting to Result

Maybe<Order> order = FindOrder(1);
Result<Order> result = order.ToResult(Error.NotFound("Order.Missing", "Order not found."));

// Result -> Maybe
Result<Order> result2 = new Order();
Maybe<Order> maybe = result2.AsMaybe();

ToResult and ToUnitResult are aliases of ToMaybeResult and ToMaybeUnitResult. Without an error argument a None becomes the Maybe.Result NotFound error. Always pass the error to ToResult(error), or call ToMaybeResult(): maybe.ToResult() with no argument binds the generic ToResult<TValue>(this TValue value) of CSharpEssentials.Results and returns a successful Result<Maybe<T>>. AsMaybe on a failed Result<T> gives None and drops the errors. The async forms are ToResultAsync, ToUnitResultAsync, ToMaybeResultAsync, ToMaybeUnitResultAsync and AsMaybeAsync.

Async

Maybe<User> user = await FindUserAsync(1);
Maybe<Order> order = await user
    .BindAsync(u => GetOrderAsync(u.Id))
    .MapAsync(o => EnrichAsync(o));

Every Maybe member that returns Task or ValueTask ends in Async: WhereAsync, ExecuteAsync, ExecuteNoValueAsync, OrAsync, MatchAsync, ToMaybeResultAsync, ToMaybeUnitResultAsync and Maybe.FromAsync (maybe.ExecuteAsync(async v => ...), task.OrAsync(() => ...)). The old unsuffixed overloads still compile as [Obsolete] forwarders and will be removed in 7.0. OrElseAsync keeps its name. An untyped async lambda on an instance ExecuteAsync, ExecuteNoValueAsync, OrAsync, WhereAsync or MatchAsync, or on a key/value MatchAsync, binds to the Task overload through OverloadResolutionPriority, which needs C# 13 or later; on C# 12, type the lambda or pass a typed local such as Func<T, Task>. The instance MapAsync has a Task and a ValueTask twin without that priority, so maybe.MapAsync(async v => ...) is ambiguous (CS0121) on every language version; pass a lambda that returns a Task instead (maybe.MapAsync(v => LoadAsync(v))) or type the delegate.

Collection Extensions

// Require ALL lookups to succeed — None if any is missing
Maybe<User[]> allUsers = userIds.Traverse(id => FindUser(id));

// Collect present values and count absences
var (values, missingCount) = maybes.Partition();

// Sequence: None if any element is None
Maybe<Config[]> configs = configMaybes.Sequence();
Method Returns Behavior
Sequence() Maybe<T[]> None if any element is None
Traverse(selector) Maybe<TOut[]> Applies selector then sequences
Partition() (T[] Values, int NoneCount) Never returns None; always splits
Choose() IEnumerable<T> Keeps the present values; Choose(selector) maps them too, ChooseAsync streams a sequence of Task<Maybe<T>>
TryFirst(), TryFirst(predicate), TryLast(), TryLast(predicate) Maybe<T> First or last element, None when there is none
dictionary.TryFind(key) Maybe<TValue> Lookup on an IReadOnlyDictionary

TryFirst is built on FirstOrDefault: for a sequence of value types such as int[], an empty sequence or a predicate without a match gives Some(default) instead of None. TryLast and the other helpers do not have this limitation.

Choosing between T?, Maybe<T> and Result<T>

Use When Example
T? At boundaries: DTOs, entity columns, JSON public string? MiddleName { get; set; }
Maybe<T> Inside the domain, when absence is an expected state composed with Map/Bind Maybe<User> user = _cache.TryFind(id);
Result<T> When the failure carries a reason the caller acts on Result<User> user = FindUser(id);
Exceptions Only for bugs and invariant violations ArgumentNullException, broken invariants
// Boundary: a nullable column converts implicitly into the domain
Maybe<string> middleName = entity.MiddleName;

// Domain: absence is expected, compose it
string display = middleName.Map(n => n.ToUpperInvariant()).GetValueOrDefault("-");

// The caller needs to know why it is missing: upgrade to Result
Result<User> found = _cache.TryFind(id).ToMaybeResult(Error.NotFound("User.NotFound", "User does not exist"));

Maybe<T>.None never equals a non-null plain T: Maybe<int>.None == 0 is false, while Maybe<int>.From(0) == 0 is true. null compares as absence: Maybe<string>.None == null is true and Maybe<string>.From("a") == null is false, in either operand order. A typed null means absence, but Equals(object?) and maybe == (object?)null follow the BCL rule that x.Equals(null) is false: Maybe<string>.None.Equals((object?)null) and Maybe<string>.None == (object?)null are both false. The static type of the operand picks the rule, so cast to T? or use HasNoValue to test for absence. Prefer HasNoValue over == null or == default for clarity. For a value type T, maybe == default means default(T) (so Maybe<int>.None == default is false), and Maybe<int> == null does not compile cleanly (CS9342, a known overload ambiguity), so use HasNoValue. ToString() returns Some(value) or None; IFormattable values are formatted with the invariant culture.

Exception-safe Creation

using System.Text.Json;

// Returns None if the factory throws; OperationCanceledException is rethrown
Maybe<int> result = Maybe<int>.FromTry(() => int.Parse(userInput));
Maybe<User> user  = Maybe<User>.FromTry(() => JsonSerializer.Deserialize<User>(json));

TapNone: Side Effect on Absence

Maybe<int> maybe = Maybe.None;

maybe.TapNone(() => logger.LogWarning("Value missing"));  // called when None

// Async
await maybe.TapNoneAsync(async () => await NotifyAsync("Missing"));

// Via Task<Maybe<T>> / ValueTask<Maybe<T>>
await GetMaybeAsync().TapNoneAsync(() => logger.LogWarning("Value missing"));

GetValueOrElse: Lazy Fallback

// Factory only called when None (unlike GetValueOrDefault which always evaluates)
int value = maybe.GetValueOrElse(() => ComputeExpensiveDefault());

Flatten

Maybe<int> inner = Maybe<Maybe<int>>.From(Maybe<int>.From(5)).Flatten(); // Some(5)

Maybe<int>? stored = null;              // e.g. a Maybe<int>? property mapped to a nullable column
Maybe<int> points = stored.Flatten();   // None

OrElse: Lazy Maybe Chain

// Alias for Or(Func<Maybe<T>>) — returns self if Some, calls factory if None
Maybe<Config> config = GetFromCache().OrElse(() => GetFromDatabase());

// Async
Maybe<Config> loaded = await GetFromCache().OrElseAsync(() => Task.FromResult(GetFromDatabase()));
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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.  net11.0 is compatible. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on CSharpEssentials.Maybe:

Package Downloads
CSharpEssentials

A comprehensive C# library enhancing functional programming capabilities with type-safe monads (Maybe, Result), discriminated unions (Any), and robust error handling. Features include: domain-driven design support, enhanced Entity Framework integration, testable time management, JSON utilities, and LINQ extensions. Built for modern C# development with focus on maintainability, testability, and functional programming principles.

CSharpEssentials.EntityFrameworkCore

Enhances Entity Framework Core with functional programming patterns and DDD-friendly features. Includes base entity classes, soft delete support, audit trails, query filters, optimistic concurrency, PostgreSQL integration, query performance monitoring, and domain event handling. Perfect for building maintainable and scalable data access layers in modern .NET applications.

CSharpEssentials.Mediator

High-performance CQRS and pipeline behaviors built on the Mediator source-generator library. Provides Result-integrated commands, queries, validation, logging, caching, and transaction behaviors with a Result-first pipeline.

CSharpEssentials.These

These<E, A> — a third result type that can carry Left (error), Right (value), or Both. Ideal for partial success scenarios, batch validation, and warning systems.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
6.5.2 172 10/10/2026
6.5.1 175 10/10/2026
6.5.0 176 10/9/2026
6.4.0 180 10/9/2026
6.3.0 176 10/8/2026
6.2.0 184 10/8/2026
6.1.0 187 10/7/2026
6.0.0 191 10/7/2026
5.2.1 190 10/7/2026
5.2.0 188 10/7/2026
5.1.0 190 10/7/2026
5.0.0 190 10/6/2026
4.1.0 192 10/6/2026
4.0.0 196 10/5/2026
3.2.3 421 5/31/2026
3.2.2 249 5/31/2026
3.2.1 217 5/31/2026
3.2.0 217 5/30/2026
3.1.0 229 5/27/2026
3.0.8 214 5/20/2026
Loading failed