ronnythedev/dotnet-clean-architecture-skills/08-dotnet-result-pattern

Claude Code and agent skill by ronnythedev/dotnet-clean-architecture-skills

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Implements the Result pattern for explicit error handling without exceptions. Provides Result, Result<T>, and Error types for clean, predictable control flow in domain-driven applications.

Install

npx vskill@latest install ronnythedev/dotnet-clean-architecture-skills/08-dotnet-result-pattern

SKILL.md

Result Pattern Implementation

Overview

The Result pattern provides explicit error handling without exceptions:

  • No exceptions for business errors - Exceptions for truly exceptional cases only
  • Explicit success/failure - Compiler forces handling of both cases
  • Composable errors - Chain operations, fail fast
  • Self-documenting - Method signatures show possible outcomes

Quick Reference

TypePurposeUsage
ResultOperation without return valueUpdate, Delete operations
Result<T>Operation with return valueCreate, Get operations
ErrorError informationCode + Description

Implementation Structure

/Domain/Abstractions/
├── Result.cs # Result and Result<T>
├── Error.cs # Error record
└── ValidationResult.cs # Multiple errors support

Template: Core Result Types

csharp
// src/{name}.domain/Abstractions/Error.cs
namespace {name}.domain.abstractions;
/// <summary>
/// Represents an error with a code and description
/// </summary>
public record Error(string Code, string Description)
{
/// <summary>
/// Represents no error (success state)
/// </summary>
public static readonly Error None = new(string.Empty, string.Empty);
/// <summary>
/// Represents a null value error
/// </summary>
public static readonly Error NullValue = new(
"Error.NullValue",
"A null value was provided");
/// <summary>
/// Creates an error from an exception
/// </summary>
public static Error FromException(Exception exception) => new(
"Error.Exception",
exception.Message);
/// <summary>
/// Implicit conversion to string (returns Code)
/// </summary>
public static implicit operator string(Error error) => error.Code;
public override string ToString() => Code;
}
csharp
// src/{name}.domain/Abstractions/Result.cs
namespace {name}.domain.abstractions;
/// <summary>
/// Represents the outcome of an operation that doesn't return a value
/// </summary>
public class Result
{
protected Result(bool isSuccess, Error error)
{
if (isSuccess && error != Error.None)
{
throw new InvalidOperationException(
"Cannot create successful result with an error");
}
if (!isSuccess && error == Error.None)
{
throw new InvalidOperationException(
"Cannot create failed result without an error");
}
IsSuccess = isSuccess;
Error = error;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Error Error { get; }
// ═══════════════════════════════════════════════════════════════
// FACTORY METHODS
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Creates a successful result
/// </summary>
public static Result Success() => new(true, Error.None);
/// <summary>
/// Creates a failed result with the specified error
/// </summary>
public static Result Failure(Error error) => new(false, error);
/// <summary>
/// Creates a successful result with a value
/// </summary>
public static Result<TValue> Success<TValue>(TValue value) =>
new(value, true, Error.None);
/// <summary>
/// Creates a failed result with the specified error
/// </summary>
public static Result<TValue> Failure<TValue>(Error error) =>
new(default, false, error);
/// <summary>
/// Creates a result based on a condition
/// </summary>
public static Result Create(bool condition, Error error) =>
condition ? Success() : Failure(error);
/// <summary>
/// Creates a result based on a condition with a value
/// </summary>
public static Result<TValue> Create<TValue>(TValue? value, Error error) =>
value is not null ? Success(value) : Failure<TValue>(error);
}
/// <summary>
/// Represents the outcome of an operation that returns a value
/// </summary>
public class Result<TValue> : Result
{
private readonly TValue? _value;
protected internal Result(TValue? value, bool isSuccess, Error error)
: base(isSuccess, error)
{
_value = value;
}
/// <summary>
/// Gets the value if successful, throws if failed
/// </summary>
public TValue Value => IsSuccess
? _value!
: throw new InvalidOperationException(
$"Cannot access value of a failed result. Error: {Error.Code}");
/// <summary>
/// Implicit conversion from value to successful Result
/// </summary>
public static implicit operator Result<TValue>(TValue? value) =>
value is not null ? Success(value) : Failure<TValue>(Error.NullValue);
/// <summary>
/// Implicit conversion from Error to failed Result
/// </summary>
public static implicit operator Result<TValue>(Error error) =>
Failure<TValue>(error);
}

Template: Result Extensions (Functional Operations)

csharp
// src/{name}.domain/Abstractions/ResultExtensions.cs
namespace {name}.domain.abstractions;
public static class ResultExtensions
{
// ═══════════════════════════════════════════════════════════════
// MAP: Transform success value
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Transforms the value if successful, preserves error if failed
/// </summary>
public static Result<TOut> Map<TIn, TOut>(
this Result<TIn> result,
Func<TIn, TOut> mapper)
{
return result.IsSuccess
? Result.Success(mapper(result.Value))
: Result.Failure<TOut>(result.Error);
}
/// <summary>
/// Async version of Map
/// </summary>
public static async Task<Result<TOut>> Map<TIn, TOut>(
this Task<Result<TIn>> resultTask,
Func<TIn, TOut> mapper)
{
var result = await resultTask;
return result.Map(mapper);
}
// ═══════════════════════════════════════════════════════════════
// BIND: Chain operations that return Result
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Chains another Result-returning operation if successful
/// </summary>
public static Result<TOut> Bind<TIn, TOut>(
this Result<TIn> result,
Func<TIn, Result<TOut>> binder)
{
return result.IsSuccess
? binder(result.Value)
: Result.Failure<TOut>(result.Error);
}
/// <summary>
/// Async version of Bind
/// </summary>
public static async Task<Result<TOut>> Bind<TIn, TOut>(
this Result<TIn> result,
Func<TIn, Task<Result<TOut>>> binder)
{
return result.IsSuccess
? await binder(result.Value)
: Result.Failure<TOut>(result.Error);
}
/// <summary>
/// Async version of Bind for Task results
/// </summary>
public static async Task<Result<TOut>> Bind<TIn, TOut>(
this Task<Result<TIn>> resultTask,
Func<TIn, Result<TOut>> binder)
{
var result = await resultTask;
return result.Bind(binder);
}
/// <summary>
/// Fully async Bind
/// </summary>
public static async Task<Result<TOut>> Bind<TIn, TOut>(
this Task<Result<TIn>> resultTask,
Func<TIn, Task<Result<TOut>>> binder)
{
var result = await resultTask;
return await result.Bind(binder);
}
// ═══════════════════════════════════════════════════════════════
// TAP: Execute side effect without changing result
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Executes an action if successful, returns original result
/// </summary>
public static Result<T> Tap<T>(
this Result<T> result,
Action<T> action)
{
if (result.IsSuccess)
{
action(result.Value);
}
return result;
}
/// <summary>
/// Async version of Tap
/// </summary>
public static async Task<Result<T>> Tap<T>(
this Result<T> result,
Func<T, Task> action)
{
if (result.IsSuccess)
{
await action(result.Value);
}
return result;
}
// ═══════════════════════════════════════════════════════════════
// MATCH: Pattern match on result
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Executes success or failure function based on result state
/// </summary>
public static TOut Match<TIn, TOut>(
this Result<TIn> result,
Func<TIn, TOut> onSuccess,
Func<Error, TOut> onFailure)
{
return result.IsSuccess
? onSuccess(result.Value)
: onFailure(result.Error);
}
/// <summary>
/// Async version of Match
/// </summary>
public static async Task<TOut> Match<TIn, TOut>(
this Task<Result<TIn>> resultTask,
Func<TIn, TOut> onSuccess,
Func<Error, TOut> onFailure)
{
var result = await resultTask;
return result.Match(onSuccess, onFailure);
}
// ═══════════════════════════════════════════════════════════════
// ENSURE: Add validation to existing result
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Validates the value and fails if predicate returns false
/// </summary>
public static Result<T> Ensure<T>(
this Result<T> result,
Func<T, bool> predicate,
Error error)
{
if (result.IsFailure)
{
return result;
}
return predicate(result.Value)
? result
: Result.Failure<T>(error);
}
/// <summary>
/// Async version of Ensure
/// </summary>
public static async Task<Result<T>> Ensure<T>(
this Result<T> result,
Func<T, Task<bool>> predicate,
Error error)
{
if (result.IsFailure)
{
return result;
}
return await predicate(result.Value)
? result
: Result.Failure<T>(error);
}
// ═══════════════════════════════════════════════════════════════
// COMBINE: Combine multiple results
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Combines multiple results, returning first failure or success
/// </summary>
public static Result Combine(params Result[] results)
{
foreach (var result in results)
{
if (result.IsFailure)
{
return result;
}
}
return Result.Success();
}
/// <summary>
/// Combines multiple results with values
/// </summary>
public static Result<(T1, T2)> Combine<T1, T2>(
Result<T1> result1,
Result<T2> result2)
{
if (result1.IsFailure) return Result.Failure<(T1, T2)>(result1.Error);
if (result2.IsFailure) return Result.Failure<(T1, T2)>(result2.Error);
return Result.Success((result1.Value, result2.Value));
}
/// <summary>
/// Combines three results with values
/// </summary>
public static Result<(T1, T2, T3)> Combine<T1, T2, T3>(
Result<T1> result1,
Result<T2> result2,
Result<T3> result3)
{
if (result1.IsFailure) return Result.Failure<(T1, T2, T3)>(result1.Error);
if (result2.IsFailure) return Result.Failure<(T1, T2, T3)>(result2.Error);
if (result3.IsFailure) return Result.Failure<(T1, T2, T3)>(result3.Error);
return Result.Success((result1.Value, result2.Value, result3.Value));
}

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
68
Repository forks
12
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 14 Jul 2026, 3 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ronnythedev/dotnet-clean-architecture-skills/08-dotnet-result-pattern/badge)](https://verified-skill.com/skills/ronnythedev/dotnet-clean-architecture-skills/08-dotnet-result-pattern)

Something wrong with this skill or its page? Tell us.