BbQ.Outcome 1.0.13

dotnet add package BbQ.Outcome --version 1.0.13
                    
NuGet\Install-Package BbQ.Outcome -Version 1.0.13
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BbQ.Outcome" Version="1.0.13" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BbQ.Outcome" Version="1.0.13" />
                    
Directory.Packages.props
<PackageReference Include="BbQ.Outcome" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BbQ.Outcome --version 1.0.13
                    
#r "nuget: BbQ.Outcome, 1.0.13"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BbQ.Outcome@1.0.13
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BbQ.Outcome&version=1.0.13
                    
Install as a Cake Addin
#tool nuget:?package=BbQ.Outcome&version=1.0.13
                    
Install as a Cake Tool

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 Error record with Code, Description, and Severity.
  • Async composition: BindAsync, MapAsync, CombineAsync for natural async pipelines.
  • LINQ integration: Native Select/SelectMany support for sync + async queries.
  • Deconstruction: Tuple-style unpacking (isSuccess, value, errors) for ergonomic handling.
  • Friendly ToString: Human-readable logging like Success: 42 or Errors: [DIV_ZERO: Division by zero].
  • Multi-targeting: Works across netstandard2.0, net6.0, and net8.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:

[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

  1. The generator scans for enums decorated with [QbqOutcome].
  2. For each enum member, it extracts:
    • Description (in priority order):
      1. [Description("...")] attribute if present
      2. <summary> from XML documentation comments
      3. Enum member name as fallback
    • Severity from [ErrorSeverity(...)] attribute (defaults to ErrorSeverity.Error)
  3. It generates a static helper class with pre-constructed Error<T> properties using named parameters.
  4. 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

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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 26 3/4/2026
1.0.12 102 1/27/2026
1.0.11 96 1/26/2026
1.0.10 682 11/28/2025
1.0.9 150 11/28/2025
1.0.8 189 11/27/2025
1.0.7 190 11/27/2025
1.0.6 188 11/26/2025
1.0.5 198 11/26/2025
1.0.4 193 11/26/2025
1.0.3 191 11/25/2025
1.0.2 194 11/23/2025
1.0.1 194 11/23/2025
1.0.0 197 11/23/2025