Extension Methods Reference
Extension methods enable fluent API composition, allowing complex workflows to be expressed as readable pipelines.
Transformation
public static class ResultExtensions
{
public static Result<TDest> Map<TSource, TDest>(
this Result<TSource> source, TDest defaultResult)
{
try
{
return source is SuccessResult<TSource>
successResult
? CreateSuccessResult(
successResult, defaultResult)
: ConvertNonSuccessTo<TSource, TDest>(source);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<TDest>(
source.SourceId,
"Exception during Map operation",
ex.Message,
source.CallerMemberName);
}
}
public static Result<TDest> Map<TSource, TDest>(
this Result<TSource> source,
Func<TSource, TDest> transform)
{
try
{
return source is SuccessResult<TSource>
successResult
? CreateSuccessResult(
successResult,
transform(successResult.Value))
: ConvertNonSuccessTo<TSource, TDest>(source);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<TDest>(
source.SourceId,
"Exception during Map operation",
ex.Message,
source.CallerMemberName);
}
}
public static async Task<Result<TDest>> MapAsync<
TSource, TDest>(
this Task<Result<TSource>> sourceTask,
Func<TSource, TDest> transform,
[CallerMemberName] string callerMemberName = "")
{
try
{
var sourceResult = await sourceTask;
return sourceResult.Map(transform);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<TDest>(
callerMemberName,
"Exception in MapAsync operation",
ex.Message,
callerMemberName);
}
}
private static SuccessResult<TDest>
CreateSuccessResult<TSource, TDest>(
Result<TSource> sourceResult,
TDest transformedValue)
{
return new SuccessResult<TDest>
{
Value = transformedValue,
SourceId = sourceResult.SourceId,
StatusCode = sourceResult.StatusCode,
CallerMemberName = sourceResult.CallerMemberName
};
}
private static Result<TDest>
ConvertNonSuccessTo<TSource, TDest>(
Result<TSource> source)
{
return source switch
{
ProblemDetailsResult<TSource> p =>
new ProblemDetailsResult<TDest>
{
SourceId = p.SourceId,
StatusCode = p.StatusCode,
ProblemDetails = p.ProblemDetails,
CallerMemberName = p.CallerMemberName
},
SuccessNullResult<TSource> sn =>
new SuccessNullResult<TDest>
{
SourceId = sn.SourceId,
StatusCode = sn.StatusCode,
CallerMemberName = sn.CallerMemberName
},
EmptyContentResult ec when
typeof(TSource) == typeof(EmptyContent) =>
typeof(TDest) == typeof(EmptyContent)
? (Result<TDest>)(object)
new EmptyContentResult
{
SourceId = ec.SourceId,
StatusCode = ec.StatusCode,
CallerMemberName = ec.CallerMemberName
}
: throw new InvalidOperationException(
$"Cannot convert EmptyContentResult " +
$"to {typeof(Result<TDest>).Name}."),
SuccessResult<TSource> =>
throw new InvalidOperationException(
$"SuccessResult cannot be handled by " +
$"{nameof(ConvertNonSuccessTo)}."),
_ => throw new InvalidOperationException(
$"Unknown non-success Result type: " +
$"{source.GetType().Name}")
};
}
}
Transformation Example
// Transform User entity to UserDto
var userResult = await GetUserAsync(id);
var dtoResult = userResult.Map(user => new UserDto
{
Id = user.Id,
FullName = $"{user.FirstName} {user.LastName}"
});
Chaining
public static class ResultExtensions
{
public static Result<TDest> FlatMap<TSource, TDest>(
this Result<TSource> source,
Func<Result<TSource>, Result<TDest>>
nextOperation,
[CallerMemberName] string callerMemberName = "")
{
try
{
return nextOperation(source);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<TDest>(
callerMemberName,
$"Exception in {nameof(FlatMap)} " +
"operation",
ex.Message,
callerMemberName);
}
}
public static async Task<Result<TDest>>
FlatMapAsync<TSource, TDest>(
this Task<Result<TSource>> sourceTask,
Func<TSource, Task<Result<TDest>>>
nextOperation,
[CallerMemberName]
string callerMemberName = "")
{
try
{
var sourceResult = await sourceTask;
return sourceResult is
SuccessResult<TSource> success
? await nextOperation(success.Value)
: ConvertNonSuccessTo<TSource, TDest>(
sourceResult);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<TDest>(
callerMemberName,
$"Exception in {nameof(FlatMapAsync)} " +
"operation",
ex.Message,
callerMemberName);
}
}
public static Task<Result<TDest>>
FlatMapAsync<TSource, TDest>(
this Result<TSource> sourceResult,
Func<TSource, Task<Result<TDest>>>
nextOperation)
{
return Task.FromResult(sourceResult)
.FlatMapAsync(nextOperation);
}
}
FlatMap is similar to Map, but is used when the transformation function itself returns a Result<T>. This prevents nested Result<Result<T>> structures, keeping the pipeline flat and composable.
Chaining Example
// Chain async operations: get user, then get their orders
// GetOrdersForUserAsync returns Result<Orders>, not Orders
var ordersResult = await GetUserAsync(userId)
.FlatMapAsync(user => GetOrdersForUserAsync(user.Id));
// Without FlatMap, this would create Result<Result<Orders>>
Validation
public static class ResultExtensions
{
public static Result<T> RejectIf<T>(
this Result<T> source,
Func<T, bool> when,
Func<Result<T>> errorFactory,
[CallerMemberName] string callerMemberName = "")
{
try
{
if (source is SuccessResult<T> success)
{
return when(success.Value)
? errorFactory()
: success;
}
return source;
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<T>(
callerMemberName,
$"Exception in {nameof(RejectIf)} " +
"operation",
ex.Message,
callerMemberName);
}
}
public static async Task<Result<T>>
RejectIfAsync<T>(
this Task<Result<T>> sourceTask,
Func<T, bool> when,
Func<Result<T>> errorFactory,
[CallerMemberName]
string callerMemberName = "")
{
try
{
var source = await sourceTask;
return source.RejectIf(
when, errorFactory, callerMemberName);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<T>(
callerMemberName,
$"Exception in {nameof(RejectIfAsync)} " +
"operation",
ex.Message,
callerMemberName);
}
}
public static Result<T> Ensure<T>(
this Result<T> source,
Func<T, bool> condition,
Func<Result<T>> errorFactory,
[CallerMemberName] string callerMemberName = "")
{
try
{
if (source is SuccessResult<T> success)
{
return condition(success.Value)
? success
: errorFactory();
}
return source;
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<T>(
callerMemberName,
$"Exception in {nameof(Ensure)} " +
"operation",
ex.Message,
callerMemberName);
}
}
public static async Task<Result<T>> EnsureAsync<T>(
this Task<Result<T>> sourceTask,
Func<T, bool> condition,
Func<Result<T>> errorFactory,
[CallerMemberName] string callerMemberName = "")
{
try
{
var source = await sourceTask;
return source.Ensure(
condition, errorFactory, callerMemberName);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<T>(
callerMemberName,
$"Exception in {nameof(EnsureAsync)} " +
"operation",
ex.Message,
callerMemberName);
}
}
}
Validation Example
// Ensure age is valid, reject if negative
var result = userResult
.Ensure(
user => user.Age >= 0,
() => ResultFactory.Problem<User>(
"UserService",
400,
("Age", "Age cannot be negative")))
.RejectIf(
user => user.Age > 150,
() => ResultFactory.Problem<User>(
"UserService",
400,
("Age", "Age seems unrealistic")));
Side Effects
public static class ResultExtensions
{
public static Result<T> OnAny<T>(
this Result<T> sourceResult,
Action<Result<T>> onAnyAction,
[CallerMemberName] string callerMemberName = "")
{
try
{
onAnyAction(sourceResult);
return sourceResult;
}
catch (Exception sideEffectEx)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<T>(
callerMemberName,
"Exception in OnAny side-effect " +
"operation",
sideEffectEx.Message,
callerMemberName);
}
}
public static async Task<Result<T>> OnAnyAsync<T>(
this Task<Result<T>> sourceTask,
Action<Result<T>> onAnyAction,
[CallerMemberName] string callerMemberName = "")
{
try
{
var result = await sourceTask;
onAnyAction(result);
return result;
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<T>(
callerMemberName,
"Exception in OnAnyAsync operation",
ex.Message,
callerMemberName);
}
}
public static Result<T> OnSuccess<T>(
this Result<T> sourceResult,
Action<T> onSuccessAction)
{
return sourceResult.OnAny(result =>
{
if (result is SuccessResult<T> success)
{
onSuccessAction(success.Value);
}
});
}
public static Task<Result<T>> OnSuccessAsync<T>(
this Task<Result<T>> sourceTask,
Action<T> onSuccessAction)
{
return sourceTask.OnAnyAsync(result =>
{
if (result is SuccessResult<T> success)
{
onSuccessAction(success.Value);
}
});
}
public static Task<Result<T>> OnErrorAsync<T>(
this Task<Result<T>> sourceTask,
Action<ProblemDetails, HttpStatusCode>
onErrorAction)
{
return sourceTask.OnAnyAsync(result =>
{
if (result is ProblemDetailsResult<T> problem)
{
onErrorAction(
problem.ProblemDetails,
problem.StatusCode);
}
});
}
}
Side Effects Example
// Log success or error for monitoring
var result = await ProcessOrderAsync(orderId)
.OnSuccessAsync(order =>
_logger.LogInformation(
"Order {OrderId} processed", order.Id))
.OnErrorAsync((problem, status) =>
_logger.LogError(
"Order processing failed: {Detail}",
problem.Detail));
Combination
public static class ResultExtensions
{
public static Result<TResult>
CombineWith<T1, T2, TResult>(
this Result<T1> first,
Result<T2> second,
Func<T1, T2, TResult> selector,
[CallerMemberName]
string callerMemberName = "")
{
try
{
if (first is not SuccessResult<T1>
firstSuccess)
return first.ConvertErrorResult<
T1, TResult>();
if (second is not SuccessResult<T2>
secondSuccess)
return second.ConvertErrorResult<
T2, TResult>();
var combinedValue = selector(
firstSuccess.Value,
secondSuccess.Value);
return new SuccessResult<TResult>
{
SourceId = firstSuccess.SourceId,
StatusCode = HttpStatusCode.OK,
Value = combinedValue
};
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<TResult>(
callerMemberName,
$"Exception in {nameof(CombineWith)} " +
"operation",
ex.Message,
callerMemberName);
}
}
public static async Task<Result<TResult>>
CombineWithAsync<T1, T2, TResult>(
this Task<Result<T1>> firstTask,
Task<Result<T2>> secondTask,
Func<T1, T2, TResult> selector,
[CallerMemberName]
string callerMemberName = "")
{
try
{
var first = await firstTask;
var second = await secondTask;
return first.CombineWith(
second, selector, callerMemberName);
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<TResult>(
callerMemberName,
$"Exception in " +
$"{nameof(CombineWithAsync)} " +
"operation",
ex.Message,
callerMemberName);
}
}
public static Result<TDest>
ConvertErrorResult<TSource, TDest>(
this Result<TSource> source)
{
return source switch
{
SuccessResult<TSource> or
SuccessResult<TSource> =>
throw new InvalidOperationException(
$"Cannot handle SuccessResult in " +
$"{nameof(ConvertErrorResult)} method"),
ProblemDetailsResult<TSource> result =>
new ProblemDetailsResult<TDest>
{
SourceId = result.SourceId,
StatusCode = result.StatusCode,
ProblemDetails = result.ProblemDetails,
CallerMemberName = result.CallerMemberName
},
_ => throw new InvalidOperationException(
$"Unknown non-ProblemDetails " +
$"{nameof(Result<TSource>)} type for " +
$"{nameof(ConvertErrorResult)}: " +
$"{source.GetType().Name}")
};
}
}
Combination Example
// Combine user and address results into a complete profile
var profileResult = await GetUserAsync(userId)
.CombineWithAsync(
GetAddressAsync(userId),
(user, address) => new UserProfile
{
Name = user.Name,
Street = address.Street,
City = address.City
});
Error Recovery
public static class ResultExtensions
{
public static async Task<Result<T>> IfErrorAsync<T>(
this Task<Result<T>> sourceTask,
Func<ProblemDetails, HttpStatusCode, bool> when,
Func<ProblemDetails, HttpStatusCode,
Task<Result<T>>> then,
[CallerMemberName] string callerMemberName = "")
{
try
{
var sourceResult = await sourceTask;
return sourceResult is
ProblemDetailsResult<T> problem &&
when(problem.ProblemDetails,
problem.StatusCode)
? await then(
problem.ProblemDetails,
problem.StatusCode)
: sourceResult;
}
catch (Exception ex)
{
return ResultFactory.Common
.InternalServerErrorWithTitle<T>(
callerMemberName,
"Exception in OrElseAsync operation",
ex.Message,
callerMemberName);
}
}
public static Result<TResult>
TryOperation<T, TResult>(
this Result<T> source,
Func<T, TResult> operation,
Func<Exception, Result<TResult>> errorFactory,
[CallerMemberName]
string callerMemberName = "")
{
try
{
if (source is not SuccessResult<T> success)
return source.ConvertErrorResult<
T, TResult>();
var result = operation(success.Value);
return new SuccessResult<TResult>
{
SourceId = success.SourceId,
StatusCode = HttpStatusCode.OK,
Value = result
};
}
catch (Exception ex)
{
return errorFactory(ex);
}
}
public static async Task<Result<TResult>>
TryOperationAsync<T, TResult>(
this Task<Result<T>> sourceTask,
Func<T, TResult> operation,
Func<Exception, Result<TResult>>
errorFactory,
[CallerMemberName]
string callerMemberName = "")
{
try
{
var source = await sourceTask;
return source.TryOperation(
operation, errorFactory, callerMemberName);
}
catch (Exception ex)
{
return errorFactory(ex);
}
}
public static async Task<Result<TResult>>
TryOperationAsync<T, TResult>(
this Task<Result<T>> sourceTask,
Func<T, Task<TResult>> operation,
Func<Exception, Result<TResult>>
errorFactory,
[CallerMemberName]
string callerMemberName = "")
{
try
{
var source = await sourceTask;
if (source is not SuccessResult<T> success)
return source.ConvertErrorResult<
T, TResult>();
var result = await operation(success.Value);
return new SuccessResult<TResult>
{
SourceId = success.SourceId,
StatusCode = HttpStatusCode.OK,
Value = result
};
}
catch (Exception ex)
{
return errorFactory(ex);
}
}
}
Error Recovery Example
// Retry on 503 Service Unavailable
var result = await CallExternalServiceAsync()
.IfErrorAsync(
when: (problem, status) =>
status == HttpStatusCode.ServiceUnavailable,
then: (problem, status) =>
Task.Delay(1000)
.ContinueWith(_ =>
CallExternalServiceAsync()));
See Also
10 November 2025