Result<T, E>
Structure
1public readonly struct Result<T, E>
2{
3 public bool IsSuccess { get; }
4 public bool IsFailure { get; }
5 public T Value { get; }
6 public E Error { get; }
7}
Factory Methods
Success
Creates a successful result with a value.
1public static Result<T, E> Success(T value)
2
3// Example
4var result = Result<int, string>.Success(42);
Failure
Creates a failed result with an error.
1public static Result<T, E> Failure(E error)
2
3// Example
4var result = Result<int, string>.Failure("Not found");
Implicit Conversions
1// From value (success)
2Result<int, string> result = 42;
3
4// From error (failure)
5Result<int, string> result = "Error message";
Static Factory Methods
SuccessIf
Creates success if condition is true, otherwise failure.
1public static Result<T, E> SuccessIf(bool condition, T value, E error)
2public static Result<T, E> SuccessIf(Func<bool> condition, T value, E error)
3
4// Example
5var result = Result.SuccessIf(age >= 18, age, "Too young");
FailureIf
Creates failure if condition is true, otherwise success.
1public static Result<T, E> FailureIf(bool condition, T value, E error)
2public static Result<T, E> FailureIf(Func<bool> condition, T value, E error)
3
4// Example
5var result = Result.FailureIf(string.IsNullOrEmpty(name), name, "Name required");
Of
Wraps exception-throwing code into Result.
1public static Result<T, E> Of<T, E>(Func<T> func, Func<Exception, E> errorFactory)
2
3// Example
4var result = Result.Of(
5 () => int.Parse(input),
6 ex => $"Invalid number: {ex.Message}"
7);
Combine
Combines multiple Results into one (no value).
1// 2-5 parameters
2public static Result<Unit, E> Combine<E>(params Result<Unit, E>[] results)
3
4// Example
5var combined = Result.Combine(result1, result2, result3);
CombineValues
Combines multiple Results preserving values.
1// 2 parameters → tuple
2public static Result<(T1, T2), E> CombineValues<T1, T2, E>(
3 Result<T1, E> result1,
4 Result<T2, E> result2)
5
6// 3-5 parameters → tuple
7public static Result<(T1, T2, T3), E> CombineValues<T1, T2, T3, E>(...)
8
9// Array → array
10public static Result<T[], E> CombineValues<T, E>(params Result<T, E>[] results)
11
12// Example
13var combined = Result.CombineValues(loadName, loadAge, loadEmail);
14// Returns: Result<(string, int, string), Error>
Map
Transforms success value.
1public Result<TNew, E> Map<TNew>(Func<T, TNew> mapper)
2
3// Example
4Result<int, string> age = 25;
5Result<string, string> category = age.Map(a => a >= 18 ? "Adult" : "Minor");
MapError
Transforms error value.
1public Result<T, ENew> MapError<ENew>(Func<E, ENew> errorMapper)
2
3// Example
4Result<int, string> result = "Not found";
5Result<int, ErrorDto> dto = result.MapError(e => new ErrorDto { Message = e });
MapSafe
Map with exception handling.
1public Result<TNew, E> MapSafe<TNew>(
2 Func<T, TNew> mapper,
3 Func<Exception, E> errorHandler)
4
5// Example
6result.MapSafe(
7 data => ProcessData(data), // Might throw
8 ex => $"Processing failed: {ex.Message}"
9);
Bind
Chains operations that return Result.
1public Result<TNew, E> Bind<TNew>(Func<T, Result<TNew, E>> binder)
2
3// Example
4Result<User, string> GetUser(int id) { }
5Result<Profile, string> GetProfile(User user) { }
6
7var profile = GetUser(123).Bind(user => GetProfile(user));
BindSafe
Bind with exception handling.
1public Result<TNew, E> BindSafe<TNew>(
2 Func<T, Result<TNew, E>> binder,
3 Func<Exception, E> errorHandler)
Validation Methods
Ensure
Adds validation predicate.
1public Result<T, E> Ensure(Func<T, bool> predicate, E error)
2public Result<T, E> Ensure(Func<T, bool> predicate, Func<T, E> errorFactory)
3
4// Example
5result
6 .Ensure(x => x > 0, "Must be positive")
7 .Ensure(x => x < 100, x => $"Value {x} is too large");
Side Effect Methods
Tap
Executes action on success without changing result.
1public Result<T, E> Tap(Action<T> onSuccess)
2public Result<T, E> Tap(Action<T> onSuccess, Action<E> onFailure)
3
4// Example
5result
6 .Tap(value => Debug.Log($"Success: {value}"))
7 .Tap(
8 onSuccess: value => SaveToCache(value),
9 onFailure: error => LogError(error)
10 );
TapSafe
Tap with exception handling.
1public Result<T, E> TapSafe(
2 Action<T> onSuccess,
3 Func<Exception, E> errorHandler)
Execute
Executes action and returns void.
1public void Execute(Action<T> onSuccess)
2public void Execute(Action<T> onSuccess, Action<E> onFailure)
3
4// Example
5result.Execute(
6 onSuccess: value => UpdateUI(value),
7 onFailure: error => ShowError(error)
8);
Pattern Matching
Match
Transforms Result to single value.
1public TResult Match<TResult>(
2 Func<T, TResult> onSuccess,
3 Func<E, TResult> onFailure)
4
5// Example
6string message = result.Match(
7 onSuccess: value => $"Success: {value}",
8 onFailure: error => $"Error: {error}"
9);
Finally
Terminal operation with Result access.
1public TResult Finally<TResult>(Func<Result<T, E>, TResult> finalizer)
2
3// Example
4var outcome = result.Finally(r =>
5{
6 LogResult(r);
7 return r.IsSuccess ? "OK" : "Failed";
8});
Error Recovery
OrElse
Provides alternative Result on failure.
1public Result<T, E> OrElse(Func<Result<T, E>> fallback)
2
3// Example
4var result = LoadFromCache()
5 .OrElse(() => LoadFromDisk())
6 .OrElse(() => LoadFromNetwork());
Or
Provides default value on failure.
1public T Or(T defaultValue)
2public T Or(Func<T> defaultFactory)
3
4// Example
5var value = LoadConfig().Or(new DefaultConfig());
TryGet
Attempts to get value/error with out parameters.
1public bool TryGetValue(out T value)
2public bool TryGetError(out E error)
3
4// Example
5if (result.TryGetValue(out var value))
6{
7 Process(value);
8}
GetValueOrThrow
Gets value or throws exception.
1public T GetValueOrThrow()
2public T GetValueOrThrow(string errorMessage)
3
4// Example
5var value = result.GetValueOrThrow("Operation failed");
Async Extensions (with NOPE_UNITASK)
Async Creation
1public static async UniTask<Result<T, E>> Of<T, E>(
2 Func<UniTask<T>> asyncFunc,
3 Func<Exception, E> errorFactory)
4
5// Example
6var result = await Result.Of(
7 async () => await FetchDataAsync(),
8 ex => $"Fetch failed: {ex.Message}"
9);
Map (Async)
Async transformation using the same method name.
1// Sync result → async transform
2public async UniTask<Result<TNew, E>> Map<TNew>(
3 Func<T, UniTask<TNew>> asyncMapper)
4
5// Async result → async transform
6public static async UniTask<Result<TNew, E>> Map<T, TNew, E>(
7 this UniTask<Result<T, E>> resultTask,
8 Func<T, UniTask<TNew>> asyncMapper)
9
10// Example
11var result = await LoadData()
12 .Map(async data => await ProcessAsync(data));
Bind (Async)
Async chaining using the same method name.
1// Sync result → async binder
2public async UniTask<Result<TNew, E>> Bind<TNew>(
3 Func<T, UniTask<Result<TNew, E>>> asyncBinder)
4
5// Async result → async binder
6public static async UniTask<Result<TNew, E>> Bind<T, TNew, E>(
7 this UniTask<Result<T, E>> resultTask,
8 Func<T, UniTask<Result<TNew, E>>> asyncBinder)
9
10// Example
11var result = await ValidateInput(input)
12 .Bind(async valid => await SaveAsync(valid));
Tap (Async)
Async side effects using the same method name.
1public async UniTask<Result<T, E>> Tap(
2 Func<T, UniTask> onSuccessAsync)
3
4public async UniTask<Result<T, E>> Tap(
5 Func<T, UniTask> onSuccessAsync,
6 Func<E, UniTask> onFailureAsync)
Ensure (Async)
Async validation using the same method name.
1public async UniTask<Result<T, E>> Ensure(
2 Func<T, UniTask<bool>> asyncPredicate,
3 E error)
Extension Methods
EnableDebug (PRO feature)
1public DebugResult<T, E> EnableDebug(string flowName)
2
3// Example
4var result = LoadData()
5 .EnableDebug("DataLoad")
6 .Bind(ProcessData);
Complete Example
1public class UserService
2{
3 public async UniTask<Result<UserDto, ApiError>> GetUserAsync(string userId)
4 {
5 return await Result.SuccessIf(
6 !string.IsNullOrEmpty(userId),
7 userId,
8 new ApiError(400, "Invalid user ID"))
9 .EnableDebug($"GetUser_{userId}")
10 .Bind(id => FetchUserFromApiAsync(id))
11 .Ensure(
12 user => ValidateUserAsync(user),
13 new ApiError(403, "User validation failed"))
14 .Map(user => EnrichUserDataAsync(user))
15 .Map(user => new UserDto
16 {
17 Id = user.Id,
18 Name = user.FullName,
19 Email = user.Email
20 })
21 .Tap(dto => CacheUserAsync(dto))
22 .MapError(error =>
23 {
24 LogError(error);
25 return new ApiError(500, "Internal error", error);
26 });
27 }
28}