LeanResults 0.1.0
dotnet add package LeanResults --version 0.1.0
NuGet\Install-Package LeanResults -Version 0.1.0
<PackageReference Include="LeanResults" Version="0.1.0" />
<PackageVersion Include="LeanResults" Version="0.1.0" />
<PackageReference Include="LeanResults" />
paket add LeanResults --version 0.1.0
#r "nuget: LeanResults, 0.1.0"
#:package LeanResults@0.1.0
#addin nuget:?package=LeanResults&version=0.1.0
#tool nuget:?package=LeanResults&version=0.1.0
LeanResults
A lightweight Result pattern for .NET with composable sync and async mapping.
Version 0.1.0 supports Result<T>, Error, Map, and Bind. More combinators (Tap, Match, …) can be added later without changing the core model.
Installation
dotnet add package LeanResults
Or pack and install locally:
dotnet pack src/LeanResults/LeanResults.csproj -c Release
dotnet add package LeanResults --source ./src/LeanResults/bin/Release
Basic usage
using LeanResults;
Result<Order> result = order;
// Error converts implicitly to Result<T>.Failure(...)
Result<Order> missing = new Error(
"Order.NotFound",
"The order could not be found.");
Inspect the outcome:
if (result.IsSuccess)
{
Console.WriteLine(result.Value);
}
else
{
Console.WriteLine($"{result.Error!.Code}: {result.Error.Message}");
}
Sync Map
Map transforms a successful value into another value (which is wrapped in Result<TOut>):
Result<Order> result = Result<Order>.Success(order);
Result<OrderDto> dto = result.Map(order =>
new OrderDto(order.Id, order.Total));
Async Map
Map over an already-available Result<T> with an async function:
Result<OrderDto> dto = await result.Map(async order =>
{
var customer = await GetCustomerAsync(order.CustomerId);
return new OrderDto(order.Id, customer.Name, order.Total);
});
Chaining from Task<Result<T>>
Map also works directly on Task<Result<T>>, so async pipelines stay fluent:
Result<OrderDto> result = await GetOrderAsync(id)
.Map(order =>
new OrderDto(order.Id, order.Total));
With an async mapper:
Result<OrderDto> result = await GetOrderAsync(id)
.Map(async order =>
{
var customer = await GetCustomerAsync(order.CustomerId);
return new OrderDto(
order.Id,
customer.Name,
order.Total);
});
Bind
Bind (flat-map) chains an operation that already returns Result<T>. Unlike Map, the binder's result is returned as-is — no extra wrapping.
Result<Order> charged = CreateOrder(request)
.Bind(order => ChargeOrder(order));
Method groups work too:
Result<Order> charged = CreateOrder(request)
.Bind(ChargeOrder);
Async binder / Task<Result<T>>:
Result<string> shipped = await CreateOrderAsync(request)
.Bind(ChargeOrderAsync)
.Map(order => $"Shipped {order.Id}");
Use Map when the next step returns a plain value.
Use Bind when the next step returns Result<T> (and can fail).
Failure propagation
If the result is a failure, the mapper/binder is not invoked. The same Error instance is propagated to Result<TOut>.Failure(...):
Result<Order> failed = new Error("Order.NotFound", "The order could not be found.");
Result<OrderDto> mapped = failed.Map(order =>
new OrderDto(order.Id, order.Total));
// mapped.IsFailure == true
// mapped.Error is the same Order.NotFound error
// the mapper never ran
Exceptions thrown inside a mapper or binder are not converted to Result failures — they propagate normally.
Null handling
| API | Null policy |
|---|---|
Result<T>.Failure(error) |
error must not be null |
new Error(code, message) |
code and message must not be null |
Map / Bind |
mapper/binder (and the receiver) must not be null |
Result<T>.Success(value) |
value may be null for reference / nullable types |
Why allow Success(null)?
A successful outcome can legitimately carry no payload (for example Result<string?> after an optional lookup). Rejecting null would force awkward sentinel values and fight nullable reference types. Structural consistency is still guaranteed: success never has an Error, failure never has a meaningful Value.
Prefer Result<T> when T should be non-null, and Result<T?> when null is a valid success value.
Building, testing, and packing
dotnet restore
dotnet build
dotnet test
dotnet pack -c Release
The NuGet package is written to src/LeanResults/bin/Release/.
Project layout
LeanResults/
├── LeanResults.sln
├── README.md
├── src/
│ └── LeanResults/
│ ├── LeanResults.csproj
│ ├── Result.cs
│ ├── Error.cs
│ └── ResultExtensions.cs
└── tests/
└── LeanResults.Tests/
├── LeanResults.Tests.csproj
├── ResultTests.cs
├── MapTests.cs
└── BindTests.cs
Package metadata placeholders
Before publishing, replace the placeholders in LeanResults.csproj:
YOUR_NAME— package authorshttps://github.com/YOUR_USERNAME/LeanResults— repository URL
| Product | Versions 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. |
-
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 |
|---|---|---|
| 0.1.0 | 114 | 8/5/2026 |