BbQ.Outcome
1.0.13
dotnet add package BbQ.Outcome --version 1.0.13
NuGet\Install-Package BbQ.Outcome -Version 1.0.13
<PackageReference Include="BbQ.Outcome" Version="1.0.13" />
<PackageVersion Include="BbQ.Outcome" Version="1.0.13" />
<PackageReference Include="BbQ.Outcome" />
paket add BbQ.Outcome --version 1.0.13
#r "nuget: BbQ.Outcome, 1.0.13"
#:package BbQ.Outcome@1.0.13
#addin nuget:?package=BbQ.Outcome&version=1.0.13
#tool nuget:?package=BbQ.Outcome&version=1.0.13
Outcome
A modern C# functional result type inspired by ErrorOr.
It builds on the excellent foundation of ErrorOr by adding async workflows, LINQ query syntax, deconstruction, Source Link, and multi-targeting — making error-aware programming feel like a first-class citizen in modern .NET.
❓ Why Outcome?
ErrorOr pioneered the idea of replacing exceptions with a discriminated union of either a value or errors.
Outcome takes this idea further:
- Structured errors: Rich
Errorrecord withCode,Description, andSeverity. - Async composition:
BindAsync,MapAsync,CombineAsyncfor natural async pipelines. - LINQ integration: Native
Select/SelectManysupport for sync + async queries. - Deconstruction: Tuple-style unpacking
(isSuccess, value, errors)for ergonomic handling. - Friendly ToString: Human-readable logging like
Success: 42orErrors: [DIV_ZERO: Division by zero]. - Multi-targeting: Works across
netstandard2.0,net6.0, andnet8.0. - Source Link enabled: Step directly into source when debugging NuGet packages.
- Source generator support: Auto-generate
Error<T>helper properties from enums with the[QbqOutcome]attribute.
💡 Example
var query =
from x in ParseAsync("10")
from y in DivideAsync(x, 2)
select y * 2;
var (ok, value, errors) = await query;
Console.WriteLine(ok
? $"Result: {value}"
: $"Errors: {string.Join("; ", errors.Select(e => e.Description))}");
Output:
Result: 10
💾 Installation
dotnet add package BbQ.Outcome
🧩 Source Generator: Error Helper Properties
The [QbqOutcome] attribute enables automatic generation of Error<TCode> helper properties for enums. This eliminates boilerplate and keeps error definitions DRY.
Usage
Mark your error enum with [QbqOutcome]:
[QbqOutcome]
public enum ApiErrorCode
{
/// <summary>
/// The requested resource was not found.
/// </summary>
NotFound,
/// <summary>
/// The user does not have permission to access this resource.
/// </summary>
Unauthorized,
/// <summary>
/// An internal server error occurred.
/// </summary>
InternalError
}
The source generator automatically creates a static class ApiErrorCodeErrors with helper properties:
// Generated code (do not edit)
public static class ApiErrorCodeErrors
{
public static Error<ApiErrorCode> NotFoundError =>
new Error<ApiErrorCode>(
Code: ApiErrorCode.NotFound,
Description: "The requested resource was not found.",
Severity: ErrorSeverity.Error
);
public static Error<ApiErrorCode> UnauthorizedError =>
new Error<ApiErrorCode>(
Code: ApiErrorCode.Unauthorized,
Description: "The user does not have permission to access this resource.",
Severity: ErrorSeverity.Error
);
public static Error<ApiErrorCode> InternalErrorError =>
new Error<ApiErrorCode>(
Code: ApiErrorCode.InternalError,
Description: "An internal server error occurred.",
Severity: ErrorSeverity.Error
);
}
Custom Descriptions
You can specify error descriptions in two ways:
1. Using XML Documentation (Recommended)
[QbqOutcome]
public enum ApiErrorCode
{
/// <summary>
/// The requested resource was not found.
/// </summary>
NotFound
}
2. Using [Description] Attribute
[QbqOutcome]
public enum ApiErrorCode
{
[System.ComponentModel.Description("Resource not found")]
NotFound
}
The generator prioritizes [Description] attributes over XML comments. If neither is provided, it uses the enum member name as a fallback.
Custom Severity Levels
By default, all generated errors use ErrorSeverity.Error. You can customize the severity for individual enum members using the [ErrorSeverity(...)] attribute:
[QbqOutcome]
public enum ApiErrorCode
{
/// <summary>
/// Validation failed.
/// </summary>
[ErrorSeverity(ErrorSeverity.Validation)]
ValidationFailed,
/// <summary>
/// The requested resource was not found.
/// </summary>
NotFound, // Uses default ErrorSeverity.Error
/// <summary>
/// An internal server error occurred.
/// </summary>
[ErrorSeverity(ErrorSeverity.Critical)]
InternalError
}
Generated code respects the specified severity levels:
// Generated code (do not edit)
public static class ApiErrorCodeErrors
{
public static Error<ApiErrorCode> ValidationFailedError =>
new Error<ApiErrorCode>(
Code: ApiErrorCode.ValidationFailed,
Description: "Validation failed.",
Severity: ErrorSeverity.Validation // Custom severity
);
public static Error<ApiErrorCode> NotFoundError =>
new Error<ApiErrorCode>(
Code: ApiErrorCode.NotFound,
Description: "The requested resource was not found.",
Severity: ErrorSeverity.Error // Default severity
);
public static Error<ApiErrorCode> InternalErrorError =>
new Error<ApiErrorCode>(
Code: ApiErrorCode.InternalError,
Description: "An internal server error occurred.",
Severity: ErrorSeverity.Critical // Custom severity
);
}
Available Severity Levels
The ErrorSeverity enum provides the following levels:
Info: Informational message; does not indicate a failure.Validation: Validation failure; the operation did not meet required conditions.Warning: Warning; the operation may have succeeded but with unexpected side effects.Error: Standard error; the operation failed and the error should be handled. (default)Critical: Critical error; the system may be in an inconsistent state.
Benefits
- Zero boilerplate: No manual
Error<T>construction. - Documentation-driven: Descriptions are extracted from XML doc comments (
<summary>tags) or[Description]attributes. - Flexibility: Choose between explicit
[Description]attributes or self-documenting XML comments. - Severity control: Customize error severity per enum member with
[ErrorSeverity(...)]. - Consistent naming: Property names follow the pattern
{EnumMember}Error. - Type-safe: Full compile-time type checking with
Error<YourEnumType>.
How It Works
- The generator scans for enums decorated with
[QbqOutcome]. - For each enum member, it extracts:
- Description (in priority order):
[Description("...")]attribute if present<summary>from XML documentation comments- Enum member name as fallback
- Severity from
[ErrorSeverity(...)]attribute (defaults toErrorSeverity.Error)
- Description (in priority order):
- It generates a static helper class with pre-constructed
Error<T>properties using named parameters. - All special characters in descriptions are properly escaped for C# string literals.
🔁 Common Patterns
Async Pipelines with Bind
public async Task<Outcome<User>> GetAndValidateUserAsync(Guid userId)
{
return await GetUserAsync(userId)
.BindAsync(user => ValidateUserAsync(user))
.BindAsync(user => EnrichUserAsync(user));
}
Pattern Matching with Match
var outcome = await CreateUserAsync(email, name);
string message = outcome.Match(
onSuccess: user => $"Created user {user.Name}",
onError: errors => $"Failed: {string.Join(", ", errors.Select(e => e.Description))}"
);
LINQ Queries
var results = from user in GetUsersAsync()
from validated in ValidateAsync(user)
select validated;
var (ok, users, errors) = await results;
Deconstruction
var (success, value, errors) = outcome;
if (success)
{
Console.WriteLine($"Success: {value}");
}
else
{
foreach (var error in errors)
{
Console.WriteLine($"[{error.Severity}] {error.Code}: {error.Description}");
}
}
🔗 Integration with CQRS
When using BbQ.Cqrs, combine Outcome with commands and queries for comprehensive error handling:
// Error codes
[QbqOutcome]
public enum UserErrors
{
[Description("Email already in use")]
[ErrorSeverity(ErrorSeverity.Validation)]
EmailAlreadyExists
}
// Command returns Outcome<T>
public class CreateUserCommand : ICommand<Outcome<User>>
{
public string Email { get; set; }
}
// Handler uses source-generated errors
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Outcome<User>>
{
public async Task<Outcome<User>> Handle(CreateUserCommand request, CancellationToken ct)
{
if (await UserExists(request.Email))
{
return UserErrorsErrors.EmailAlreadyExistsError.ToOutcome<User>();
}
return Outcome<User>.From(await CreateUser(request.Email));
}
}
📚 Learn More
- Strongly Typed Errors Guide - Best practices for error handling
- BbQ.Cqrs Documentation - Using Outcome with CQRS
| 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
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on BbQ.Outcome:
| Package | Downloads |
|---|---|
|
BbQ.ChatWidgets
Framework-agnostic widgets for AI chat UIs, powered by Microsoft.Extensions.AI. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.13 | 0 | 3/4/2026 |
| 1.0.12 | 102 | 1/27/2026 |
| 1.0.11 | 95 | 1/26/2026 |
| 1.0.10 | 674 | 11/28/2025 |
| 1.0.9 | 149 | 11/28/2025 |
| 1.0.8 | 189 | 11/27/2025 |
| 1.0.7 | 190 | 11/27/2025 |
| 1.0.6 | 187 | 11/26/2025 |
| 1.0.5 | 198 | 11/26/2025 |
| 1.0.4 | 192 | 11/26/2025 |
| 1.0.3 | 190 | 11/25/2025 |
| 1.0.2 | 194 | 11/23/2025 |
| 1.0.1 | 193 | 11/23/2025 |
| 1.0.0 | 197 | 11/23/2025 |