Full Example & Setup
Consuming a Result
At the edge of your pipeline, use a switch expression to exhaustively handle all three cases:
var output = result.Value switch
{
Success<Todo> s => $"OK: {s.Data.Title}",
HttpError h => $"HTTP {h.StatusCode}: {h.Message}",
TransportError t => $"Transport error: {t.Exception.Message}",
_ => "Unknown"
};
The compiler enforces exhaustiveness — you cannot forget a case.
Full Example — Fetch, Transform, Chain
var result = await httpClient.GetJsonAsync<Todo>("https://api.example.com/todos/1");
var summary = await result
.MapAsync(async todo => todo with { Title = todo.Title.ToUpperInvariant() })
.ContinueWith(t => t.Result.Bind(todo =>
todo.Completed
? ApiResult.Ok($"Done: {todo.Title}")
: ApiResult.HttpFail<string>(HttpStatusCode.UnprocessableEntity, "Not completed")));
Console.WriteLine(summary.Value switch
{
Success<string> s => s.Data,
HttpError h => $"Error {h.StatusCode}: {h.Message}",
TransportError t => $"Transport: {t.Exception.Message}",
_ => "?"
});
GetJsonAsync Extension
RemoteServiceExtensions.GetJsonAsync<T> wraps an HttpClient call and returns ApiResult<T> — HTTP errors and exceptions are handled transparently:
var result = await httpClient.GetJsonAsync<Todo>(url);
No try/catch needed at the call site. Errors flow through the monad.
Requirements & Setup
| IDE | VS Code Insiders |
| Extensions | C# Dev Kit + C# — both set to Preview channel |
| Target framework | net11.0 (Update 2) |
| Language version | <LangVersion>preview</LangVersion> in .csproj |
Union types are a C# 15 preview feature. The preview lang version and preview extension channels are required for compiler support.
Enable Preview Extensions in VS Code Insiders
- Open Extensions (
Ctrl+Shift+X) - Find C# → gear icon → Switch to Pre-Release Version
- Find C# Dev Kit → gear icon → Switch to Pre-Release Version
Github repo
Source code and tests are available at: github.com/toreaurstadboss/ApiResultMonad