第9章:VO実装① Email(バリデーションの基本)📧✅
この章では「Emailは、作れた時点で正しい」を体で覚えます💪✨ (2026年1月時点の最新:.NET 10 LTS+C# 14 前提だよ〜🧁) (Microsoft)
1) この章のゴール🎯✨
- 入力された文字列を、EmailというVOに変換できる📧➡️💎
- **変なEmailは“生成できない”**ようにできる🚫
- 失敗したときに、**理由(エラーメッセージ)**も返せる🙂🧾
2) Emailの検証って、実は3段階あるの😳📮
Emailって「見た目がそれっぽい」だけじゃ足りないことがあるよ〜💦 だいたいこんな3段階👇
- 形式チェック(
@がある、空白がない…など)🧼 - 実在チェック(DNS/MXとか、ドメインが存在するか)🌍
- 到達チェック(確認メールを送って、クリックしてもらう)📩✅
この章はまず ①形式チェックだけやるよ!🙂✨ “完全なRFC準拠”を正規表現でやろうとすると沼りやすいので、やりすぎないのがコツ🫠(メールの形式は仕様上かなり幅が広いよ) (datatracker.ietf.org)
3) 今回の「最小ライン」ルール(やりすぎない)🧁✅
学内カフェ注文アプリ☕️🧾では、まずこれで十分なことが多いよ👇
- 空じゃない(null/空文字/空白だけはNG)🚫
- 前後の空白はトリムする✂️
- 途中に空白が入ってたらNG(
"a b@c.com"みたいなの)🚫 - 「表示名つき」みたいな文字列はNG(
"Alice <a@b.com>")🚫 - 形式パースは
MailAddress.TryCreateを使う(例外を投げないので扱いやすい)✨ (Microsoft Learn)
4) 実装:Email VO(TryCreateパターン)💎📧

ポイントはココ👇
- コンストラクタは隠す(勝手に作れない)🔒
- TryCreateが入口(ここで検証して、OKなら生成)🚪✨
- 値は **正規化(ここでは小文字化)**しておくと、比較がラク🧊
using System;
using System.Net.Mail;
namespace Cafe.Domain;
public readonly record struct Email
{
public string Value { get; }
private Email(string value) => Value = value;
public override string ToString() => Value;
// ✅ 失敗理由も返せる TryCreate
public static bool TryCreate(string? input, out Email email, out string error)
{
email = default;
error = "";
if (string.IsNullOrWhiteSpace(input))
{
error = "メールアドレスが空だよ〜🥺";
return false;
}
var s = input.Trim();
// 空白混入は事故りやすいので早めに弾く🙂
if (s.Contains(' '))
{
error = "メールアドレスに空白が入ってるよ〜😵";
return false;
}
// ✅ .NETのパーサで“形式として”作れるかチェック(例外なし)
if (!MailAddress.TryCreate(s, out var parsed) || parsed is null)
{
error = "メールアドレスの形式が変かも…🤔";
return false;
}
// 「Alice <a@b.com>」みたいな“表示名付き”は今回はNGにする
if (!string.Equals(parsed.Address, s, StringComparison.OrdinalIgnoreCase))
{
error = "表示名付き(例: Alice <a@b.com>)は使えないよ〜🙅♀️";
return false;
}
// ✨ 正規化:小文字に寄せる(学習用の割り切り)
email = new Email(s.ToLowerInvariant());
return true;
}
// ✅ “作れなかったら例外”版(ドメイン内で雑に使いたいとき用)
public static Email Create(string input)
{
if (TryCreate(input, out var email, out var error))
return email;
throw new ArgumentException(error, nameof(input));
}
}
MailAddress.TryCreate は「作れないときに例外を投げない」Try系なので、入力検証に向いてるよ〜✨ (Microsoft Learn)
5) 使い方イメージ(注文の連絡先Email)☕️🧾📧
「画面入力→VO化→ドメインは安心」って流れを作るとスッキリするよ✨
using Cafe.Domain;
public static class CreateOrderUseCase
{
public static void Execute(string emailText)
{
if (!Email.TryCreate(emailText, out var email, out var error))
{
// UIに優しく返す(ここではConsoleで代用)
Console.WriteLine(error);
return;
}
// ここから先は「Emailは正しい」前提で進められる✨
Console.WriteLine($"注文者メール: {email}");
}
}
6) テスト(xUnit)🧪✨
「無効なEmailは作れない」をテストで守るのがVOの気持ちよさ💎
using Xunit;
using Cafe.Domain;
public class EmailTests
{
[Theory]
[InlineData("a@b.com", "a@b.com")]
[InlineData(" USER@Example.com ", "user@example.com")]
[InlineData("user.name+tag@sub.example.ac.jp", "user.name+tag@sub.example.ac.jp")]
public void TryCreate_Valid_ReturnsTrue(string input, string expected)
{
var ok = Email.TryCreate(input, out var email, out var error);
Assert.True(ok, error);
Assert.Equal(expected, email.Value);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("a@")]
[InlineData("@b.com")]
[InlineData("a b@c.com")]
[InlineData("Alice <a@b.com>")]
public void TryCreate_Invalid_ReturnsFalse(string? input)
{
var ok = Email.TryCreate(input, out _, out var error);
Assert.False(ok);
Assert.False(string.IsNullOrWhiteSpace(error));
}
[Fact]
public void Equality_IsCaseInsensitive_ByNormalization()
{
Email.TryCreate("USER@EXAMPLE.COM", out var e1, out _);
Email.TryCreate("user@example.com", out var e2, out _);
Assert.Equal(e1, e2);
}
}
7) ミニ演習(10〜15分)✍️⏱️✨
-
Email.TryCreate("a@b.com")が成功するのを確認📧✅ -
Email.TryCreate("Alice <a@b.com>")が失敗するのを確認🙅♀️ -
NGケースを 5個追加してテストに入れる🧪✨
- 例:
"a@b..com"、"a@b,com"、"a@@b.com"、"a@b.com "、" a@b.com"など🙂
- 例:
8) AI活用(Copilot/Codex想定)🤖✨:最短で精度を上げるコツ
そのまま貼って使えるプロンプト例だよ👇💕
- 「C# 14で Email の Value Object を
readonly record structで作って。TryCreateでエラー文字列も返して。MailAddress.TryCreateを使って、Alice <a@b.com>は弾いて。xUnit テストも一緒に」 - 「EmailのNGテストケースを20個、現実で起きがちなやつ中心で提案して」
✅ AIの出力を見たら、ここだけは目視チェックしてね👀
- コンストラクタがpublicになってない?(勝手に作れて崩れる)🔒
- 正規表現ゴリゴリになってない?(やりすぎ)🫠
- エラーメッセージが雑すぎない?(UIに出すとつらい)🥺
9) よくある落とし穴😵💫💥(先に潰そ!)
- 「RFCに完全準拠する正規表現」を目指して時間が溶ける🫠(仕様は広いよ) (datatracker.ietf.org)
EmailAddressAttributeだけで“完全に安心”と思い込む(用途は主に入力注釈寄り。実装は変わることもある)📌 (Microsoft Learn)- “到達するか”まで形式チェックで保証しようとする(それは確認メールの領域)📩✅
まとめ🎀✨
- Emailはまず 「作れた時点で正しい」VOにする📧💎
- 検証は 最小ラインでOK(やりすぎない)🙂
TryCreate+テストで、入力地獄を入口で止める🧱🧪- 次の章(Money)でも同じ型の考え方でいけるよ〜!💰✨
必要なら、このEmail VOを Order(注文)に組み込むところまで一気に“章の続き”として書くよ〜☕️🧾💪