Skip to content

ResultT API Reference

ferdi kurnaz edited this page Aug 27, 2026 · 2 revisions

Result<T> API Reference

This page lists every public method on the generic Result<T> type (used when an operation returns a value on success, for example Result<User> or Result<int>).

Result<T> has everything that Result has, plus a Value property and a few extra methods.

The Value property

public T Value { get; }

Holds the actual result value. It is only meaningful when IsSuccess is true. On failure, Value is the default value for the type (for example null for reference types, or 0 for int).

Tip: Always check IsSuccess (or use Match) before reading Value, to avoid using a meaningless default value by mistake.

Creating a Result<T>

Result<T>.Success(T value)

Result<int> result = Result<int>.Success(42);

Result<T>.Failure(Error error)

Result<int> result = Result<int>.Failure(new Error("Parse.Failed", "Not a number."));

Implicit conversions

You can skip the explicit calls above:

Result<int> result = 42;                                  // same as Success(42)
Result<int> result2 = new Error("Parse.Failed", "...");   // same as Failure(error)

This makes it very easy to return a plain value or a plain error from a method that is declared to return Result<T>.

Turning exceptions into a Result<T>

Result<T>.Try(Func<T> func)

Runs func. If it succeeds, wraps the returned value in a successful Result<T>. If it throws, the exception becomes the Error of a failed Result<T>.

Result<int> result = Result<int>.Try(() => int.Parse("10"));

Result<T>.TryAsync(Func<Task<T>> func)

Same idea, for asynchronous code.

Result<User> result = await Result<User>.TryAsync(async () =>
{
    return await _repository.GetByIdAsync(id);
});

Reading the outcome

Match(Action<T> onSuccess, Action<Error> onFailure)

result.Match(
    onSuccess: user => Console.WriteLine($"Found: {user.Name}"),
    onFailure: error => Console.WriteLine($"Error: {error.Description}")
);

Match<TResult>(Func<T, TResult> onSuccess, Func<Error, TResult> onFailure)

string message = result.Match(
    onSuccess: user => $"Found: {user.Name}",
    onFailure: error => $"Error: {error.Description}"
);

MatchAsync(Func<T, Task> onSuccess, Func<Error, Task> onFailure)

An asynchronous version of Match. Use it when your success/failure branches need to await something.

await result.MatchAsync(
    onSuccess: user => _notifier.SendWelcomeEmailAsync(user),
    onFailure: error => _logger.LogErrorAsync(error.Description)
);

MatchAsync<TResult>(Func<T, Task<TResult>> onSuccess, Func<Error, Task<TResult>> onFailure)

Same idea, but each branch returns a Task<TResult>, so you get a final awaited value.

string message = await result.MatchAsync(
    onSuccess: user => Task.FromResult($"Found: {user.Name}"),
    onFailure: error => Task.FromResult($"Error: {error.Description}")
);

Transforming and chaining

Map(Action<T> action)

If successful, runs action using the current Value, then returns a plain successful Result (no value). Useful when you just need a side effect (like logging), and want to "downgrade" a Result<T> to a plain Result.

Result result = userResult.Map(user => Console.WriteLine(user.Name));

Map<TResult>(Func<T, TResult> func)

If successful, transforms the current Value into a new value of type TResult, wrapped in a new Result<TResult>. If the original result failed, the failure passes through unchanged.

Result<string> nameResult = userResult.Map(user => user.Name);

MapAsync(Func<T, Task> action)

An asynchronous version of Map(Action<T>). If successful, awaits action using the current Value, then returns a successful plain Result. Returns a Task<Result>.

Result result = await userResult.MapAsync(user => _auditLog.RecordAsync(user.Id));

MapAsync<TResult>(Func<T, Task<TResult>> func)

An asynchronous version of Map<TResult>. If successful, awaits func and wraps the returned value in a Result<TResult>. Returns a Task<Result<TResult>>.

Result<string> nameResult = await userResult.MapAsync(async user =>
{
    return await _translator.TranslateAsync(user.Name);
});

Bind(Func<T, Result> func)

If successful, calls func with the current Value. func returns a plain Result. Use this when the next step doesn't need to return a value, like deleting something.

Result deleteResult = userResult.Bind(user => _repository.Delete(user.Id));

Bind<TResult>(Func<T, Result<TResult>> func)

If successful, calls func with the current Value. func returns another Result<TResult>. This lets you chain multiple value-producing steps together.

Result<Order> orderResult = userResult.Bind(user => CreateOrderForUser(user));

BindAsync(Func<T, Task<Result>> func)

An asynchronous version of Bind(Func<T, Result>). If successful, awaits func with the current Value. Returns a Task<Result>.

Result deleteResult = await userResult.BindAsync(user => _repository.DeleteAsync(user.Id));

BindAsync<TResult>(Func<T, Task<Result<TResult>>> func)

An asynchronous version of Bind<TResult>. If successful, awaits func with the current Value, and returns its Result<TResult>. Returns a Task<Result<TResult>>.

Result<Order> orderResult = await userResult.BindAsync(user => CreateOrderForUserAsync(user));

Chaining tip: Because every ...Async method returns a Task, you can chain several of them together and await once at the end. See the updated Async Operations page for a full example that mixes sync and async steps in one pipeline.

Map vs. Bind — what's the difference?

This is a common question, so here is a simple rule:

  • Use Map when your next step is a plain function that cannot fail and just returns a normal value (or void/an action).
  • Use Bind when your next step already returns a Result or Result<T> (it can succeed or fail on its own).
// Map: int -> string (a plain transformation)
Result<string> textResult = numberResult.Map(n => n.ToString());

// Bind: string -> Result<User> (a step that can itself fail)
Result<User> userResult = textResult.Bind(text => FindUserByName(text));

If you use Map where you should use Bind, you would end up with something like Result<Result<User>> (a "result inside a result"), which is confusing. Bind avoids that by "flattening" the chain.

Summary table

Method Purpose
Success(T value) Create a successful Result<T>
Failure(Error) Create a failed Result<T>
Try(Func<T>) Catch exceptions, return Result<T>
TryAsync(Func<Task<T>>) Same as Try, for async code
Match(Action<T>, Action<Error>) React to success/failure, no return value
Match<TResult>(Func<T,TResult>, Func<Error,TResult>) React to success/failure, return a value
MatchAsync(Func<T,Task>, Func<Error,Task>) Async version of Match, no return value
MatchAsync<TResult>(Func<T,Task<TResult>>, Func<Error,Task<TResult>>) Async version of Match, returns a value
Map(Action<T>) Run a side effect, downgrade to plain Result
MapAsync(Func<T,Task>) Async version of Map(Action<T>)
Map<TResult>(Func<T,TResult>) Transform the value into a new Result<TResult>
MapAsync<TResult>(Func<T,Task<TResult>>) Async version of Map<TResult>
Bind(Func<T, Result>) Chain to a step that returns a plain Result
BindAsync(Func<T, Task<Result>>) Async version of Bind
Bind<TResult>(Func<T, Result<TResult>>) Chain to a step that returns another Result<TResult>
BindAsync<TResult>(Func<T, Task<Result<TResult>>>) Async version of Bind<TResult>

Next: see Chaining with Map and Bind for a step-by-step walkthrough.

Clone this wiki locally