Pragmatic.Result
Zero-allocation Result types for .NET 10 with railway-oriented programming.
The Problem
Section titled “The Problem”Most .NET code signals failure by throwing. That hides error paths from method signatures, forces
callers to guess what to catch, and allocates expensive stack traces for expected outcomes like
“entity not found”. The compiler can’t enforce handling — miss a catch and you ship a generic 500.
// Without Pragmatic.Result: error paths are invisible, exceptions are expensivepublic async Task<User> GetUserAsync(int id){ var user = await _repository.FindByIdAsync(id); if (user is null) throw new NotFoundException($"User {id} not found"); // hidden, expensive if (!user.IsActive) throw new BusinessRuleException("User is inactive"); // hidden, expensive return user;}The Solution
Section titled “The Solution”Make error paths explicit in the type system. Return Result<TValue, TError> instead of throwing —
the compiler ensures callers handle both outcomes. The wrapper itself is zero-allocation.
public async Task<Result<User, NotFoundError>> GetUserAsync(int id){ var user = await _repository.FindByIdAsync(id); if (user is null) return NotFoundError.Create("User", id); // implicit conversion to failure return user; // implicit conversion to success}
// Caller: exhaustive, compiler-enforcedvar message = result.Match( user => $"Found: {user.Name}", error => $"Not found: {error.EntityId}");Features
Section titled “Features”- Zero-allocation core —
Result<TValue, TError>,Result<TValue>,VoidResult,VoidResult<TError>, andMaybe<TValue>are allreadonly struct. - Multi-error Results —
Result<TValue, TError1, …, TError8>andVoidResult<TError1, …, TError8>variants for up to 8 typed errors per operation, shipped pre-generated in the package. - Railway-oriented composition —
Map,Bind,Match,Tap,Ensure,Recover,OrElse, each with async counterparts (MapAsync,BindAsync,MatchAsync, …) that chain onTask<Result>. - Collection operators —
Combine(fail-fast),CollectAll(accumulate intoAggregateError),Partition/GetSuccesses/GetFailures, plusIAsyncEnumerablevariants. - Exception boundary —
Result.Try/Result.TryAsyncwrap throwing code;Result.FromNullableandMaybe.ToResultlift nullables into the Result world. - Rich error model —
Errorrecord base withCode,StatusCode,Title,Parameters,IsTransient/RetryAfter, andWriteExtensionsfor structured ProblemDetails payloads. - 8 built-in HTTP error types —
BadRequestError(400),UnauthorizedError(401),ForbiddenError(403),NotFoundError(404),ConflictError(409),BusinessRuleError(422),InternalServerError(500),DependencyError(502/503/504), each with factory methods. - ASP.NET Core integration — return
Resultstraight from Minimal API endpoints (WithResultHandling()) or Controllers (ResultActionFilter); failures become RFC 7807 ProblemDetails with the right status code; OpenAPI transformers document Result schemas and error responses (AddResultTypeSupport()). - JSON round-trip — System.Text.Json converters for
Result,VoidResult, andMaybe; polymorphic error payloads via the$errorTypediscriminator. - EF Core integration —
SaveChangesAsResultAsyncturnsDbUpdateExceptioninto typed database errors;FirstOrDefaultAsResultAsync/SingleOrDefaultAsResultAsync/FindAsResultAsyncreturnResultinstead of null; provider packages parse SQL Server, PostgreSQL, MySQL, and SQLite exceptions into precise error types. - Localization-ready — every error carries a stable
Codeand derivedMessageKey;IErrorMessageResolverplugs in custom/localized ProblemDetails messages. - Analyzer —
PRAG0001warns when.Valueis accessed without checkingIsSuccessfirst.
Packages
Section titled “Packages”| Package | Description |
|---|---|
Pragmatic.Result | Core Result/VoidResult/Maybe types, error model, HTTP errors, JSON converters |
Pragmatic.Result.AspNetCore | ProblemDetails, Minimal API & Controller filters, OpenAPI transformers |
Pragmatic.Result.EFCore | SaveChangesAsResultAsync, query extensions, database error types |
Pragmatic.Result.EFCore.SqlServer | SQL Server exception parser |
Pragmatic.Result.EFCore.PostgreSQL | PostgreSQL exception parser |
Pragmatic.Result.EFCore.MySql | MySQL exception parser |
Pragmatic.Result.EFCore.Sqlite | SQLite exception parser |
Pragmatic.Result.Analyzers | PRAG0001 unsafe .Value access analyzer |
Installation
Section titled “Installation”dotnet add package Pragmatic.Resultdotnet add package Pragmatic.Result.AspNetCore # optional: ASP.NET Core integrationdotnet add package Pragmatic.Result.EFCore # optional: EF Core integrationQuick Start
Section titled “Quick Start”using Pragmatic.Result;using Pragmatic.Result.Http;
Result<User, NotFoundError> result = user; // success (implicit)Result<User, NotFoundError> failed = NotFoundError.Create("User", userId);
var output = result.Match( user => $"Found: {user.Name}", error => $"Error: {error.EntityType} not found");
if (result.TryGetValue(out var value)) Console.WriteLine(value.Name);In ASP.NET Core, return Result types straight from endpoints and let the integration map them to
HTTP — success to 200, typed errors to the right status + an RFC 7807 ProblemDetails:
builder.Services.AddPragmaticResult();
var api = app.MapGroup("").WithResultHandling();api.MapGet("/users/{id}", (int id, UserService svc) => svc.GetByIdAsync(id));// Result<User, NotFoundError> → 200 with JSON, or 404 ProblemDetailsFull walkthrough: Getting Started.
Status
Section titled “Status”Stable within 1.0.0-alpha — the core Result API, error types, and the ASP.NET Core and EF Core
integrations are settled. See the roadmap.
| Concepts | Railway-oriented model, when Result beats exceptions, decision guide |
| Getting Started | Your first Result, matching, ASP.NET Core wiring |
| API Reference | Every extension (Map/Bind/Match/Tap/Ensure), error types, ProblemDetails, JSON, OpenAPI |
| Localization | IErrorMessageResolver, localized error messages |
| Migration | Moving from exceptions / other Result libraries |
| Common Mistakes | The most frequent Result pitfalls |
| Troubleshooting | Problem/solution guide |
Requirements
Section titled “Requirements”- .NET 10.0+
License
Section titled “License”Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Result is MIT-licensed.