第16章:Resultを“最小構成”で作る🧰
この章は「Result型って結局なにが嬉しいの?どう作ればいいの?」を 自分の手で腹落ちさせる回だよ😊✨ (ライブラリを使う前に、まず “仕組み” を理解しちゃおう〜!)
0) 2026の前提(情報アップデート)🗓️✨
いま(2026/01/19)の時点だと、.NET 10 は 2025/11/11 に正式リリースされた LTSで、2028/11/10 までサポートって位置づけだよ📌 (Microsoft for Developers) そして C# 14も同時期の世代で、Microsoft Learn に “What’s new” がまとまってるよ🧠 (Microsoft Learn) テストは xUnit.net v3 が .NET 8+ 対応で、VS 2026 での話も公式に載ってる🧪 (xUnit.net)
1) Resultの超ざっくりイメージ🎁✅❌

Result
- ✅ 成功:**Value(値)**を持つ
- ❌ 失敗:**Error(失敗理由)**を持つ
- そして 同時に両方は持たない(←これ大事!)💡
例:在庫チェック
- 在庫OK →
Result<int>.Success(残り数) - 在庫NG →
Result<int>.Failure(Error("OUT_OF_STOCK", "在庫がありません"))
2) この章で作る“最小セット”🧩
第16章のゴールは「最低限これだけあれば動くよね」の形👇
IsSuccess(成功?)Value(成功時の値)Error(失敗時の情報)Success(...) / Failure(...)(作り方を統一)
そして “初心者が事故りやすい点” を防ぐために、不正状態を作れないようにするよ🛡️✨ (成功なのにErrorが入ってる、みたいな矛盾を禁止!)
3) まずは Error 型(最小)🏷️
「失敗理由」を型にしておくと、あとで 分類・ログ・ProblemDetails に繋げやすくなるよ🧾✨
namespace ErrorModeling;
public sealed record Error(string Code, string Message);
Code:機械が扱う(分類・翻訳・HTTP化に強い)🤖Message:人が読む(表示文言の元)💬
4) Result(値なし版)も最小で作る(あると便利)🎁
「ただ成功/失敗だけ返したい」ケースって結構あるの。なので先に作っちゃう😊
namespace ErrorModeling;
public sealed class Result
{
private readonly Error? _error;
private Result(bool isSuccess, Error? error)
{
if (isSuccess && error is not null)
throw new ArgumentException("SuccessなのにErrorが入ってるのはNGだよ😵");
if (!isSuccess && error is null)
throw new ArgumentException("FailureなのにErrorが無いのはNGだよ😵");
IsSuccess = isSuccess;
_error = error;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Error Error =>
IsFailure ? _error! : throw new InvalidOperationException("SuccessのときはErrorは読めないよ🙅♀️");
public static Result Success() => new(isSuccess: true, error: null);
public static Result Failure(Error error) =>
new(isSuccess: false, error: error ?? throw new ArgumentNullException(nameof(error)));
}
ここでやってることはシンプルで、矛盾した状態をコンストラクタで拒否してるだけだよ👌✨
5) 本命!Result(値あり)を作る🧰✨
次は「成功なら値」「失敗ならエラー」を持つバージョン!
namespace ErrorModeling;
public sealed class Result<T>
{
private readonly T? _value;
private readonly Error? _error;
private Result(bool isSuccess, T? value, Error? error)
{
if (isSuccess && error is not null)
throw new ArgumentException("SuccessなのにErrorが入ってるのはNGだよ😵");
if (!isSuccess && error is null)
throw new ArgumentException("FailureなのにErrorが無いのはNGだよ😵");
IsSuccess = isSuccess;
_value = value;
_error = error;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public T Value =>
IsSuccess ? _value! : throw new InvalidOperationException("FailureのときはValueは読めないよ🙅♀️");
public Error Error =>
IsFailure ? _error! : throw new InvalidOperationException("SuccessのときはErrorは読めないよ🙅♀️");
public static Result<T> Success(T value) => new(isSuccess: true, value: value, error: null);
public static Result<T> Failure(Error error) =>
new(isSuccess: false, value: default, error: error ?? throw new ArgumentNullException(nameof(error)));
}
ポイント3つ🌟
ValueとErrorは 読んじゃダメな時に例外を投げる(事故を早く見つける⚡)Success/Failureの 作り方を統一(呼び出し側が迷わない🧭)- “失敗はErrorを必ず持つ” を強制(失敗理由が消えない🔎)
6) 使ってみる(超ミニ例)🧪✨
「空文字は想定内の失敗」にしてみよう〜!
using ErrorModeling;
static Result<string> ValidateName(string? name)
{
if (string.IsNullOrWhiteSpace(name))
return Result<string>.Failure(new Error("NAME_EMPTY", "名前を入力してね🌸"));
return Result<string>.Success(name.Trim());
}
// 使う側
var r = ValidateName(" こみやんま ");
if (r.IsFailure)
{
Console.WriteLine(r.Error.Message);
return;
}
Console.WriteLine($"Hello, {r.Value}!");
7) ミニ演習:ユニットテストちょっと書く🧪💖
ここは「Resultが正しく動く」証拠を作る感じ! xUnit は v3 が .NET 8+ 対応だよ🧪 (xUnit.net)
テスト例(最小)✅❌
using ErrorModeling;
using Xunit;
public class ResultTests
{
[Fact]
public void Success_should_have_value()
{
var r = Result<int>.Success(42);
Assert.True(r.IsSuccess);
Assert.Equal(42, r.Value);
}
[Fact]
public void Failure_should_have_error()
{
var r = Result<int>.Failure(new Error("NG", "だめだよ🥲"));
Assert.True(r.IsFailure);
Assert.Equal("NG", r.Error.Code);
}
[Fact]
public void Accessing_value_on_failure_should_throw()
{
var r = Result<int>.Failure(new Error("NG", "だめだよ🥲"));
Assert.Throws<InvalidOperationException>(() => _ = r.Value);
}
[Fact]
public void Accessing_error_on_success_should_throw()
{
var r = Result<int>.Success(1);
Assert.Throws<InvalidOperationException>(() => _ = r.Error);
}
}
8) よくあるつまずき(先に潰す)🧯
- 成功/失敗の両方が入る設計にしちゃう(矛盾)😵 → コンストラクタで弾くのが正解👍
Valueをnull許容にして混乱する🤔 → まずは「成功なら値がある」前提でOK。必要なら第17章で情報設計を強化しよう🧾✨- “想定内の失敗” を例外で投げ続ける💥 → Resultに寄せると「呼び出し側が読むだけで分かるコード」になっていくよ📖✨
9) AI活用(この章で効く使い方)🤖🧑🏫
Copilot / Codex に投げるときは、お願いの型を揃えるとめちゃ強いよ💪✨
① テストケース候補を増やす🧪
「Result
② 不正状態レビュー役👀
「この Result
③ 例外メッセージ改善💬
「初心者が読んで分かる例外メッセージにしたい。短くてやさしい文言を提案して」
10) この章のゴール達成チェック✅✨
できたら勝ち〜!🎉
- ✅
ResultとResult<T>が作れた - ✅ 不正状態が作れない(コンストラクタで防御)
- ✅ 成功/失敗で
Value/Errorの読み分けができる - ✅ xUnitで最低4本テストを書けた🧪
次の 第17章では、このResultに「どんな情報を持たせると運用・UXが強くなるか」🧾🧠(code/message/detail/retryable/action みたいなやつ)を育てていくよ〜😊✨