MonadicSharp 1.4.0
dotnet add package MonadicSharp --version 1.4.0
NuGet\Install-Package MonadicSharp -Version 1.4.0
<PackageReference Include="MonadicSharp" Version="1.4.0" />
<PackageVersion Include="MonadicSharp" Version="1.4.0" />
<PackageReference Include="MonadicSharp" />
paket add MonadicSharp --version 1.4.0
#r "nuget: MonadicSharp, 1.4.0"
#:package MonadicSharp@1.4.0
#addin nuget:?package=MonadicSharp&version=1.4.0
#tool nuget:?package=MonadicSharp&version=1.4.0
MonadicSharp
Railway-Oriented Programming for C#. Replace exception-driven control flow with composable, explicit error handling.
dotnet add package MonadicSharp
The problem
// You never know what this can throw, or when, or why
public User CreateUser(string name, string email)
{
if (string.IsNullOrWhiteSpace(name))
throw new ValidationException("Name is required");
if (!email.Contains('@'))
throw new ValidationException("Invalid email");
var user = _db.Users.FirstOrDefault(u => u.Email == email);
if (user != null)
throw new ConflictException("Email already exists");
return _db.Save(new User(name, email));
}
Every caller needs a try/catch. Errors are invisible in the signature. Composing multiple operations that can fail is painful.
The solution
public Result<User> CreateUser(string name, string email)
{
return ValidateName(name)
.Bind(_ => ValidateEmail(email))
.Bind(_ => CheckEmailNotTaken(email))
.Map(_ => new User(name, email))
.Bind(user => _db.Users.AddAsync(user));
}
The signature tells the truth. Errors propagate automatically. No exceptions, no hidden branches.
Core types
Result<T> — success or failure
Result<int> Parse(string input) =>
int.TryParse(input, out var n)
? Result<int>.Success(n)
: Result<int>.Failure(Error.Validation("Not a number", field: "input"));
var result = Parse("42")
.Map(n => n * 2)
.Where(n => n < 200, "Value too large")
.Match(
onSuccess: n => $"Result: {n}",
onFailure: err => $"Error: {err.Message}"
);
// "Result: 84"
Option<T> — value or nothing
Option<User> FindUser(int id) =>
_db.TryGetValue(id, out var user) ? Option<User>.Some(user) : Option<User>.None;
string name = FindUser(42)
.Map(u => u.Name.ToUpper())
.GetValueOrDefault("Anonymous");
Error — structured, composable errors
// Semantic factory methods
Error.Validation("Name is required", field: "name")
Error.NotFound("User", identifier: id.ToString())
Error.Forbidden("Admin access required")
Error.Conflict("Email already registered", resource: "email")
Error.FromException(ex)
// Enrich with context
Error.Create("Payment failed")
.WithMetadata("orderId", order.Id)
.WithMetadata("amount", order.Total)
.WithInnerError(gatewayError)
// Combine multiple errors
Error.Combine(nameError, emailError, ageError)
Either<TLeft, TRight> — two explicit tracks
Either<Error, User> Authenticate(string token) =>
_tokenService.Validate(token)
? Either<Error, User>.FromRight(_users.GetByToken(token))
: Either<Error, User>.FromLeft(Error.Forbidden("Invalid token"));
var message = Authenticate(token).Match(
onLeft: err => $"Denied: {err.Message}",
onRight: user => $"Welcome, {user.Name}"
);
Async pipelines
Chain async operations with automatic short-circuiting on failure:
var result = await GetOrderAsync(orderId) // Task<Result<Order>>
.Then(ValidateInventoryAsync) // stops here if invalid
.Then(ReserveStockAsync)
.Then(SendConfirmationEmailAsync)
.ExecuteAsync();
Conditional steps
await ProcessOrder(order)
.ThenIf(o => o.Total > 1000, ApplyDiscountAsync)
.ThenIf(o => o.IsInternational, CalculateShippingAsync)
.ExecuteAsync();
Built-in retry
await GetOrder(id)
.ThenWithRetry(
operation: CallExternalPaymentApiAsync,
maxAttempts: 3,
delay: TimeSpan.FromSeconds(2))
.ExecuteAsync();
Entity Framework Core integration
DbSetExtensions wraps EF Core operations to return Result<T> and Option<T> instead of throwing:
// FindAsync returns Option<T> — no null checks needed
Option<User> user = await _db.Users.FindAsync(id);
// AddAsync returns Result<T>
Result<User> created = await _db.Users.AddAsync(newUser);
// Composable pipeline with EF
var result = await _db.Users.FindAsync(id)
.ToResult(Error.NotFound("User", id.ToString()))
.BindAsync(user => _db.Users.Update(updatedUser))
.BindAsync(_ => _db.SaveChangesAsync());
Collecting multiple errors
Sequence and Traverse let you run all validations and collect every failure — not just the first one:
var errors = new[]
{
ValidateName(name),
ValidateEmail(email),
ValidateAge(age)
}.Sequence();
errors.Match(
onSuccess: values => Save(values),
onFailure: err => ReturnAllErrors(err.GetAllErrors())
);
Project templates
Scaffold a full project pre-wired with MonadicSharp:
dotnet new install MonadicSharp.Templates
dotnet new monadic-api -n MyApi # Minimal API + Result pattern + EF Core
dotnet new monadic-clean -n MyApp # Clean Architecture + CQRS + MediatR
Requirements
- .NET 8.0+
- C# 10.0+
Contributing
Issues and pull requests are welcome. See CHANGELOG for version history.
License
MIT — see LICENSE.
| Product | Versions 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 was computed. 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 was computed. 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. |
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.2)
- System.Text.Json (>= 8.0.5)
NuGet packages (9)
Showing the top 5 NuGet packages that depend on MonadicSharp:
| Package | Downloads |
|---|---|
|
MonadicSharp.AutoMapper
Lightweight, functional-friendly object mapper for MonadicSharp. Provides mapping configuration and extensions for Option, Result, and Either. |
|
|
MonadicSharp.DI
MonadicSharp.DI - lightweight private mediator (in-process) aligned with MonadicSharp functional primitives (Result, Option, etc.). |
|
|
MonadicSharp.AI
AI-specific extensions for MonadicSharp: typed error handling for LLM APIs, exponential backoff retry, agent execution tracing, structured output validation, and streaming completion support. The functional reliability layer for enterprise .NET AI systems. |
|
|
MonadicSharp.Telemetry
Observability layer for MonadicSharp.Agents — OpenTelemetry-compatible metrics and distributed tracing for agent executions, pipelines, and circuit breakers. Zero external dependencies: built on System.Diagnostics.Metrics and System.Diagnostics.ActivitySource. |
|
|
MonadicSharp.Caching
Result-aware caching layer for MonadicSharp — cache misses and errors are first-class Result values, never null or exceptions. Supports IMemoryCache, IDistributedCache, and transparent agent output caching via CachingAgentWrapper. |
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.4.0: Added comprehensive xUnit test suite, GitHub Actions CI/CD, CHANGELOG, usage examples, and fixed package dependencies
v1.3.0: Complete rebranding from FunctionalSharp to MonadicSharp - Templates, documentation, and package naming fully unified
v1.2.0: Changed namespace from FunctionalSharp to MonadicSharp to match NuGet package name
v1.1.0: Added Either<TLeft, TRight> type for representing two alternative types