Clawfoot.ResultPattern
4.0.0
See the version list below for details.
dotnet add package Clawfoot.ResultPattern --version 4.0.0
NuGet\Install-Package Clawfoot.ResultPattern -Version 4.0.0
<PackageReference Include="Clawfoot.ResultPattern" Version="4.0.0" />
<PackageVersion Include="Clawfoot.ResultPattern" Version="4.0.0" />
<PackageReference Include="Clawfoot.ResultPattern" />
paket add Clawfoot.ResultPattern --version 4.0.0
#r "nuget: Clawfoot.ResultPattern, 4.0.0"
#:package Clawfoot.ResultPattern@4.0.0
#addin nuget:?package=Clawfoot.ResultPattern&version=4.0.0
#tool nuget:?package=Clawfoot.ResultPattern&version=4.0.0
Clawfoot.ResultPattern
A Results pattern library for .NET: return success/failure and propagate errors across layers without throwing. Use Result and Result<T> instead of exceptions for expected failure paths.
Results are immutable. Every operation returns a new instance; chain with Invoke, WithError, WithValue, or combine with Result.Combine(...).
Null-safe. A successful Result<T> always carries a non-null value, and the library is fully annotated for nullable reference types: after a HasErrors/Success check the compiler knows Value is not null. No ! needed.
Target: .NET Standard 2.1. Upgrading from 3.x? See CHANGELOG.md.
Install
dotnet add package Clawfoot.ResultPattern
Quick usage
using Clawfoot.ResultPattern;
// Success (no value): a plain Result
Result result = Result.Ok();
if (!result) return result;
// Success with value: return the value, it converts implicitly
Result<User> FindUser(Guid id)
{
User? user = _db.Find(id);
if (user is null) return Result.Error("Not found", userMessage: "We couldn't find that user.");
return user;
}
// Consuming: check, then use Value. The compiler knows it's not null.
Result<User> userResult = FindUser(id);
if (userResult.HasErrors)
return userResult; // or (Result)userResult to propagate into another Result<TOther>
Console.WriteLine(userResult.Value.Name); // no '!'
Results and values
| You have | Use |
|---|---|
| Success, no value | Result (Result.Ok()) |
| Success with a value | Result<T>: return the value, or Result.Ok(value) |
| Success where the value may legitimately be absent ("found nothing") | (Result, T?) tuple, or a plain Result plus an out/nullable value |
| Failure | Result.Error(...), which converts to any Result<T> |
The rules, enforced by annotations, analyzers and at runtime:
- A successful
Result<T>always has a non-nullValue. Creating one with null throwsArgumentNullException, and the compiler warns first ([DisallowNull]). - A failed result converts freely to any
Result<T>, which is how errors propagate up the stack. - A successful plain
Resultcannot become aResult<T>, because there is no value to put in it.return Result.Ok();in aResult<T>method is a compile error (CFRESULT003) and throwsInvalidOperationExceptionat runtime.
Reading the value
if (result.HasErrors) return result; // or: if (!result.Success) / !result.IsOk / !result.HasResult
use(result.Value);
if (result.TryGetValue(out var value)) use(value);
// Tests and startup code: throws InvalidOperationException listing the errors
User user = CreateUser().GetValueOrThrow();
Assert.True(result.Success) and Assert.False(result.HasErrors) (xUnit) also tell the compiler the value is present. Fluent assertion chains like result.HasErrors.Should().BeFalse() don't, so use GetValueOrThrow() there.
Immutable patterns
Assign the returned result to keep chaining or combining:
// Add an error → new result (a Result<T> keeps its value)
result = result.WithError("Validation failed");
// Combine multiple results → new result (errors combined; for Result<T>, last successful value wins)
Result combined = Result.Combine(result1, result2);
Result<int> combinedT = Result.Combine(r1, r2, r3);
Result<User> withValidation = Result.Combine(validationResult, userResult);
// Chain Invoke (returns new result on exception)
result = result.Invoke(() => ValidateInput(input)).Invoke(() => SaveToDb(entity));
// InvokeResult returns Result<T>
Result<int> r = start.InvokeResult(() => GetCount());
Error handling in a flow
Use Invoke / InvokeAsync to run code and capture exceptions into a new result (no mutation):
var result = Result.Ok()
.Invoke(() => ValidateInput(input))
.Invoke(() => SaveToDb(entity));
if (!result) return result;
// Async
var result = await Result.Ok()
.InvokeAsync(async () => await FetchAndSaveAsync());
Enum-based errors
Decorate an enum with [Error] and create results from it:
public enum UserErrors
{
[Error(Code = 404, Message = "User not found", UserMessage = "We couldn't find that user.")]
NotFound,
[Error(Code = 400, Message = "Invalid id")]
InvalidId
}
Result r = Result.FromError(UserErrors.NotFound);
Analyzers
The package includes Roslyn analyzers. They load automatically, with no extra package to install.
| ID | Default | Flags |
|---|---|---|
CFRESULT001 |
Warning | Discarded result of With*, Invoke, InvokeAsync, Combine (results are immutable, so the call does nothing) |
CFRESULT002 |
Warning | result.Value!. Check HasErrors/Success instead, or use GetValueOrThrow() in tests |
CFRESULT003 |
Error | A known-successful Result (Result.Ok(), new Result()) converted to Result<T> (implicitly, by cast, or with As<T>()) |
CFRESULT004 |
Warning | Result<X?>: a nullable type argument can't represent "no value"; use (Result, X?) |
Raise or lower severities in .editorconfig:
[*.cs]
dotnet_diagnostic.CFRESULT002.severity = error
dotnet_diagnostic.CFRESULT004.severity = error
Features
- Result and Result<T>: immutable;
Success,IsOk,HasErrors,HasResult,Errors,Message,Value,TryGetValue,GetValueOrThrow - Nullable flow:
HasErrors/Success/IsOk/HasResult/TryGetValuetell the compiler whenValueis non-null - Result.Combine(...): combines multiple results (errors combined; for
Result<T>, last successful value wins) - WithError / WithErrors / WithException / WithValue: return a new result with added state
- Invoke / InvokeAsync / InvokeResult: wrap calls and return a new result (chainable)
- Error and IError: structured errors (Code, Message, UserMessage, GroupName, MemberName)
- ErrorAttribute and Error.From(enum) for enum-driven error messages
- Implicit conversion of
Resulttobool(if (!result) ...)
License
LGPL-3.0-only. © 2026 Douglas Gaskell / Clawfoot Software
| Product | Versions 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 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. |
| .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. |
-
.NETStandard 2.1
- System.Threading.Tasks.Extensions (>= 4.5.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.