Core Operations
4.1.1. 💡 Why Result Instead of Exceptions?
| ❌ Exceptions | ✅ Result Pattern | |
|---|---|---|
| Visibility | Hidden in method signatures | Explicit in return type |
| Surprises | Runtime surprises | Compile-time guarantees |
| Code | Complex try-catch blocks | Simple pattern matching |
| Testing | Hard to test error paths | Easy to test both paths |
| Composition | Break the call chain | Chain operations cleanly |
4.1.2. 🧠 Functional Programming Foundation
Railway-Oriented Programming (ROP) - Immutable Results: Thread-safe functional data structures - Error Composition: Chain operations without exception handling - Success/Failure Pipelines: Clean separation of happy and error paths - Type Safety: Compile-time guarantees for error handling
4.1.3. 🔧 Complete Method Catalog
4.1.3.1. Core Operations
// Factory Methods
Result<T>.Ok(value) // Success result
Result<T>.Fail("error") // Failure result
Result.Fail("error") // Non-generic failure
// Pattern Matching
result.Match(
onSuccess: value => DoSomething(value),
onFailure: errors => HandleErrors(errors)
);
// Value Access
result.Value // Throws if failed
result.GetValueOrDefault(defaultValue) // Safe access
4.1.3.2. Functional Composition
// Bind (Chain operations)
var result = Result<int>.Ok(5)
.Bind(x => Result<string>.Ok(x.ToString()))
.Bind(s => ValidateEmail(s));
// Map (Transform success values)
var result = Result<int>.Ok(5)
.Map(x => x * 2)
.Map(x => x.ToString());
// Tap (Side effects without changing result)
var result = Result<User>.Ok(user)
.Tap(u => LogUserAccess(u))
.Tap(u => SendNotification(u));
// Ensure (Validation)
var result = Result<string>.Ok(email)
.Ensure(e => IsValidEmail(e), "Invalid email format")
.EnsureAsync(async e => !await EmailExistsAsync(e), "Email already registered");
4.1.3.3. Async Operations
// All methods have async variants
var result = await Result<int>.Ok(id)
.BindAsync(async i => await GetUserAsync(i))
.MapAsync(async user => await ToDtoAsync(user))
.TapAsync(async dto => await LogAccessAsync(dto))
.EnsureAsync(async dto => await ValidateDtoAsync(dto), "Invalid DTO");
4.1.3.4. Async Patterns (WhenAll, Retry, Timeout)
// Run multiple async results concurrently — typed tuples!
var result = await Result.WhenAll(GetUser(id), GetAccount(id));
var (user, account) = result.Value;
// Retry with exponential backoff
var result = await Result.Retry(
() => CallExternalApi(),
maxRetries: 3,
delay: TimeSpan.FromSeconds(1),
backoffFactor: 2.0);
// Enforce time limits
var result = await GetSlowData().Timeout(TimeSpan.FromSeconds(5));
4.1.4. 📊 LINQ Integration
Functional Query Comprehensions
// LINQ-like syntax for Result operations
var result = from user in GetUser(id)
from validation in ValidateUser(user)
from saved in SaveUser(validation)
from notification in SendNotification(saved)
select saved;
// Complex queries
var results = from id in userIds
from user in GetUserAsync(id)
from updated in UpdateUserAsync(user)
select updated;
// Equivalent to method chaining
var result = GetUser(id)
.Bind(ValidateUser)
.Bind(SaveUser)
.Bind(SendNotification);
4.1.5. 🎯 Advanced Patterns
4.1.5.1. Maybe - Null-Safe Optionals
// Instead of null references
Maybe<User> user = GetUserFromCache(id);
var email = user
.Select(u => u.Email)
.Filter(email => email.Contains("@"))
.ValueOrDefault("no-reply@example.com");
// Safe operations
var result = user
.Map(u => u.Name)
.Bind(name => ValidateName(name))
.ToResult(() => new UserNotFoundError(id));
4.1.5.2. OneOf - Discriminated Unions
// Internal OneOf implementation
OneOf<ValidationError, User> result = ValidateAndCreateUser(request);
return result.Match(
case1: error => BadRequest(error),
case2: user => Ok(user)
);
// Three-type OneOf
OneOf<ValidationError, NotFoundError, User> GetUser(int id) { /* logic */ }
// Conversion to Result
var result = oneOf.ToResult(); // Convert OneOf to Result
4.1.5.3. Validation Rules Framework
// Built-in validation
var validator = Validator.Create<User>()
.Rule(u => u.Email, email => email.Contains("@"))
.Rule(u => u.Name, name => !string.IsNullOrWhiteSpace(name))
.Rule(u => u.Age, age => age >= 18, "Must be 18 or older");
var result = validator.Validate(user);
4.1.5.4. JSON Serialization (System.Text.Json)
using REslava.Result.Serialization;
// Register converters once
var options = new JsonSerializerOptions();
options.AddREslavaResultConverters();
// Result<T> serialization
var result = Result<User>.Ok(new User("Alice", "alice@test.com"));
var json = JsonSerializer.Serialize(result, options);
// {"isSuccess":true,"value":{"name":"Alice","email":"alice@test.com"},"errors":[],"successes":[]}
var deserialized = JsonSerializer.Deserialize<Result<User>>(json, options);
// OneOf<T1,T2> serialization
OneOf<Error, User> oneOf = OneOf<Error, User>.FromT2(user);
var json2 = JsonSerializer.Serialize(oneOf, options);
// {"index":1,"value":{"name":"Alice","email":"alice@test.com"}}
// Maybe<T> serialization
var maybe = Maybe<string>.Some("hello");
var json3 = JsonSerializer.Serialize(maybe, options);
// {"hasValue":true,"value":"hello"}
4.1.6. 🔧 CRTP Pattern & Method Chaining
Curiously Recurring Template Pattern
// Fluent method chaining with CRTP
var result = Result<User>.Ok(user)
.Ensure(ValidateEmail)
.Map(ToDto)
.Tap(SendWelcomeEmail)
.Bind(SaveToDatabase)
.WithSuccess("User created successfully")
.WithTag("UserId", user.Id);
4.1.7. 🔄 Advanced Extensions
Functional Composition
// Function composition
var createUser = Compose(
ValidateRequest,
MapToUser,
SaveUser,
SendNotification
);
// Higher-order functions
var results = users
.Where(u => u.IsActive)
.Select(u => ProcessUser(u))
.Sequence(); // Turns IEnumerable<Result<T>> into Result<IEnumerable<T>>
// Traverse operations
var results = userIds
.Traverse(id => GetUserAsync(id)); // Async version of Sequence
4.1.8. 🚫 Tap on Failure — TapOnFailure
Execute a side-effect only when the result has failed — logging, metrics, alerting. The result passes through unchanged:
Result<User> result = await GetUserAsync(id)
.TapOnFailure(error => _logger.LogWarning("GetUser failed: {Msg}", error.Message));
// Async side-effect
Result<Order> order = await CreateOrderAsync(request)
.TapOnFailureAsync(async error => await _metrics.IncrementAsync("order.failed"));
// Non-generic Result
Result operation = await DeleteUserAsync(id)
.TapOnFailure(error => _audit.RecordFailure("delete_user", error));
Combine with Tap() for full success/failure observability in one chain:
Result<Order> order = await CreateOrderAsync(request)
.Tap(o => _logger.LogInformation("Order {Id} created", o.Id))
.TapOnFailure(e => _logger.LogError("Order creation failed: {Msg}", e.Message));
4.1.9. 🔀 Conditional Factories — OkIf / FailIf
Create results directly from boolean conditions — no if/else boilerplate:
// Result (no value)
Result r1 = Result.OkIf(age >= 18, "Must be 18 or older");
Result r2 = Result.FailIf(user.IsSuspended, new ValidationError("Account suspended"));
// Result<T> — value on success
Result<User> r3 = Result<User>.OkIf(user != null, user!, "User not found");
Result<User> r4 = Result<User>.FailIf(user.Age < 18, new ValidationError("Age", "Must be 18+"), user);
// Lazy evaluation — condition and/or value computed only when needed
Result<User> r5 = Result<User>.OkIf(
() => _db.IsUserActive(id), // predicate evaluated lazily
() => _db.LoadUser(id), // value only loaded when needed
"User not found");
// Async versions
Result result = await Result.OkIfAsync(() => _api.CheckExistsAsync(id), "Not found");
Result<User> result = await Result<User>.OkIfAsync(
() => _api.CheckExistsAsync(id),
() => _api.LoadUserAsync(id),
"User not found");