FluentValidation

バリデーションライブラリC#.NET企業向け高性能ドメイン駆動設計

ライブラリ

FluentValidation

概要

FluentValidationはC#/.NETエコシステムで最も信頼されているバリデーションライブラリです。「シンプルで読みやすい強型付けバリデーションルール」を理念として開発され、.NET開発者にとって最もパワフルで柔軟性の高いバリデーション体験を提供します。6億回以上のダウンロード実績を持ち、ASP.NET Core、Entity Framework、Blazorとの深い統合により、企業レベルのアプリケーション開発において標準的な選択肢となっています。

詳細

FluentValidation 12.0.0は2025年現在の最新版で、レガシーコードの削除と古いプラットフォームサポートの終了に焦点を当てた安定版リリースです。.NET 8以降に対応し、Linq Expressionを基盤とした強力な型安全バリデーションシステムを提供。AbstractValidatorベースのクラス設計により、ドメイン駆動設計(DDD)パターンと完全に統合され、複雑なビジネスルールの実装を直感的に実現します。シングルトンパターンでの使用を前提とした設計により、高いパフォーマンスを維持しています。

主な特徴

  • 強力な型安全性: C#の型システムとLambda式を最大限活用
  • ASP.NET Core統合: MVC、Web API、Blazorとの統一されたバリデーション
  • エンタープライズ対応: 複雑なビジネスルールと大規模プロジェクト向け
  • 高性能: シングルトンパターンとExpression Trees最適化
  • 豊富なバリデーター: 40以上のビルトインバリデーション機能
  • カスタマイズ性: カスタムバリデーターとルール拡張の容易さ

メリット・デメリット

メリット

  • .NETエコシステムでの圧倒的な実績と信頼性(6億回以上ダウンロード)
  • ASP.NET Core、Entity Framework、Blazorとの完璧な統合
  • 企業レベルのアプリケーションでの豊富な採用事例
  • 強力な型安全性とIntelliSenseによる開発効率向上
  • 条件付きバリデーション、ルールセット、継承対応の高度な機能
  • 充実した公式ドキュメントと活発なコミュニティサポート

デメリット

  • C#/.NET専用(他言語での使用不可)
  • 初期学習コストがやや高い(Expression Treesの理解が必要)
  • 軽量なバリデーションには機能過多の可能性
  • フロントエンド(JavaScript)との連携で追加設定が必要
  • 複雑なビジネスルールではパフォーマンス調整が必要な場合
  • .NET Framework古いバージョンのサポート終了

参考ページ

書き方の例

インストールと基本セットアップ

# FluentValidationのインストール
dotnet add package FluentValidation

# ASP.NET Core統合用(非推奨パッケージに注意)
dotnet add package FluentValidation.AspNetCore

# NuGetパッケージマネージャーを使用する場合
Install-Package FluentValidation
Install-Package FluentValidation.AspNetCore

基本的なスキーマ定義とバリデーション

using FluentValidation;

// 顧客クラスの定義
public class Customer
{
    public int Id { get; set; }
    public string Surname { get; set; }
    public string Forename { get; set; }
    public decimal Discount { get; set; }
    public string Address { get; set; }
    public bool HasDiscount { get; set; }
    public string Postcode { get; set; }
}

// 基本的なバリデータークラス
public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        // 基本的なバリデーションルール
        RuleFor(customer => customer.Surname)
            .NotEmpty()
            .WithMessage("苗字は必須項目です");
        
        RuleFor(customer => customer.Forename)
            .NotEmpty()
            .WithMessage("名前を入力してください")
            .Length(2, 50)
            .WithMessage("名前は2文字以上50文字以下で入力してください");
        
        RuleFor(customer => customer.Discount)
            .NotEqual(0)
            .When(customer => customer.HasDiscount)
            .WithMessage("割引が有効な場合は割引率を設定してください");
        
        RuleFor(customer => customer.Address)
            .Length(20, 250)
            .WithMessage("住所は20文字以上250文字以下で入力してください");
        
        RuleFor(customer => customer.Postcode)
            .Must(BeAValidPostcode)
            .WithMessage("有効な郵便番号を入力してください");
    }
    
    // カスタムバリデーションメソッド
    private bool BeAValidPostcode(string postcode)
    {
        if (string.IsNullOrEmpty(postcode))
            return false;
        
        // 日本の郵便番号形式(例: 123-4567)
        return System.Text.RegularExpressions.Regex.IsMatch(
            postcode, @"^\d{3}-\d{4}$");
    }
}

// バリデーションの実行
class Program
{
    static void Main()
    {
        var customer = new Customer
        {
            Surname = "",
            Forename = "太郎",
            HasDiscount = true,
            Discount = 0,
            Address = "短すぎる住所",
            Postcode = "無効な郵便番号"
        };
        
        var validator = new CustomerValidator();
        
        // バリデーション実行
        var results = validator.Validate(customer);
        
        if (!results.IsValid)
        {
            foreach (var failure in results.Errors)
            {
                Console.WriteLine($"プロパティ {failure.PropertyName} でエラー: {failure.ErrorMessage}");
            }
        }
        
        // 例外を投げるバリデーション
        try
        {
            validator.ValidateAndThrow(customer);
        }
        catch (ValidationException ex)
        {
            Console.WriteLine($"バリデーションエラー: {ex.Message}");
        }
    }
}

高度なバリデーションルールとカスタムバリデーター

using FluentValidation;
using FluentValidation.Results;

// 複雑な製品注文モデル
public class ProductOrder
{
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal DiscountRate { get; set; }
    public DateTime OrderDate { get; set; }
    public Address ShippingAddress { get; set; }
    public List<string> Tags { get; set; } = new List<string>();
    public OrderStatus Status { get; set; }
    public Customer Customer { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; } = "日本";
}

public enum OrderStatus
{
    Pending,
    Processing,
    Shipped,
    Delivered,
    Cancelled
}

// 高度なバリデータークラス
public class ProductOrderValidator : AbstractValidator<ProductOrder>
{
    public ProductOrderValidator()
    {
        // 製品名の詳細バリデーション
        RuleFor(order => order.ProductName)
            .NotEmpty()
            .WithMessage("製品名は必須項目です")
            .Length(2, 100)
            .WithMessage("製品名は2文字以上100文字以下で入力してください")
            .Matches(@"^[a-zA-Z0-9\s\-_]+$")
            .WithMessage("製品名は英数字、スペース、ハイフン、アンダースコアのみ使用可能です");
        
        // 数量の条件付きバリデーション
        RuleFor(order => order.Quantity)
            .GreaterThan(0)
            .WithMessage("数量は1以上である必要があります")
            .LessThanOrEqualTo(1000)
            .WithMessage("一度に注文できる数量は1000個までです")
            .Must(BeValidQuantityForProduct)
            .WithMessage("この製品の注文数量が制限を超えています");
        
        // 価格の詳細バリデーション
        RuleFor(order => order.UnitPrice)
            .GreaterThan(0)
            .WithMessage("単価は0より大きい必要があります")
            .LessThanOrEqualTo(1000000)
            .WithMessage("単価は100万円以下である必要があります")
            .ScalePrecision(2, 10)
            .WithMessage("単価は小数点以下2桁までです");
        
        // 割引率のバリデーション
        RuleFor(order => order.DiscountRate)
            .InclusiveBetween(0, 1)
            .WithMessage("割引率は0から1の間で入力してください");
        
        // 注文日のバリデーション
        RuleFor(order => order.OrderDate)
            .GreaterThanOrEqualTo(DateTime.Today.AddDays(-30))
            .WithMessage("注文日は30日以内である必要があります")
            .LessThanOrEqualTo(DateTime.Today.AddDays(7))
            .WithMessage("注文日は1週間後までしか設定できません");
        
        // ネストしたオブジェクトのバリデーション
        RuleFor(order => order.ShippingAddress)
            .SetValidator(new AddressValidator())
            .When(order => order.Status != OrderStatus.Cancelled);
        
        // コレクションのバリデーション
        RuleFor(order => order.Tags)
            .Must(tags => tags.Count <= 5)
            .WithMessage("タグは5個まで設定可能です");
        
        RuleForEach(order => order.Tags)
            .NotEmpty()
            .WithMessage("空のタグは設定できません")
            .Length(2, 20)
            .WithMessage("各タグは2文字以上20文字以下である必要があります");
        
        // 条件付きバリデーション(複数条件)
        When(order => order.Status == OrderStatus.Processing, () =>
        {
            RuleFor(order => order.Customer)
                .NotNull()
                .WithMessage("処理中の注文には顧客情報が必要です");
            
            RuleFor(order => order.ShippingAddress)
                .NotNull()
                .WithMessage("処理中の注文には配送先住所が必要です");
        }).Otherwise(() =>
        {
            RuleFor(order => order.Status)
                .NotEqual(OrderStatus.Shipped)
                .WithMessage("顧客情報なしで配送状態にはできません");
        });
        
        // 依存ルール(先行ルールが成功した場合のみ実行)
        RuleFor(order => order.ProductName)
            .NotEmpty()
            .DependentRules(() =>
            {
                RuleFor(order => order.Quantity)
                    .Must((order, quantity) => IsQuantityAvailableForProduct(order.ProductName, quantity))
                    .WithMessage("指定された製品の在庫が不足しています");
            });
        
        // ルールセットの定義
        RuleSet("QuickValidation", () =>
        {
            RuleFor(order => order.ProductName).NotEmpty();
            RuleFor(order => order.Quantity).GreaterThan(0);
        });
        
        RuleSet("CompleteValidation", () =>
        {
            Include("QuickValidation");
            RuleFor(order => order.UnitPrice).GreaterThan(0);
            RuleFor(order => order.ShippingAddress).SetValidator(new AddressValidator());
        });
    }
    
    // カスタムバリデーションメソッド
    private bool BeValidQuantityForProduct(int quantity)
    {
        // 製品固有の数量制限ロジック
        return quantity <= 100; // 簡単な例
    }
    
    private bool IsQuantityAvailableForProduct(string productName, int quantity)
    {
        // 在庫確認ロジック(実際の実装ではデータベースアクセス等)
        return !string.IsNullOrEmpty(productName) && quantity > 0;
    }
}

// 住所バリデーター
public class AddressValidator : AbstractValidator<Address>
{
    public AddressValidator()
    {
        RuleFor(address => address.Street)
            .NotEmpty()
            .WithMessage("住所は必須項目です")
            .Length(5, 200)
            .WithMessage("住所は5文字以上200文字以下で入力してください");
        
        RuleFor(address => address.City)
            .NotEmpty()
            .WithMessage("市区町村は必須項目です");
        
        RuleFor(address => address.PostalCode)
            .NotEmpty()
            .WithMessage("郵便番号は必須項目です")
            .Matches(@"^\d{3}-\d{4}$")
            .WithMessage("郵便番号は000-0000の形式で入力してください");
        
        RuleFor(address => address.Country)
            .NotEmpty()
            .WithMessage("国名は必須項目です");
    }
}

フレームワーク統合(ASP.NET Core、Blazor、Entity Framework等)

using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

// ASP.NET Core統合例
// Startup.cs または Program.cs
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // FluentValidationの登録(推奨方法)
        services.AddValidatorsFromAssemblyContaining<CustomerValidator>();
        
        // 個別バリデーターの登録
        services.AddScoped<IValidator<Customer>, CustomerValidator>();
        services.AddScoped<IValidator<ProductOrder>, ProductOrderValidator>();
        
        // ASP.NET Core MVC設定
        services.AddControllers(options =>
        {
            // モデルバリデーションの自動実行を無効化(FluentValidationを優先)
            options.ModelValidatorProviders.Clear();
        });
    }
}

// ASP.NET Core WebAPIでの使用例
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
    private readonly IValidator<Customer> _customerValidator;
    
    public CustomersController(IValidator<Customer> customerValidator)
    {
        _customerValidator = customerValidator;
    }
    
    [HttpPost]
    public async Task<IActionResult> CreateCustomer([FromBody] Customer customer)
    {
        // バリデーション実行
        var validationResult = await _customerValidator.ValidateAsync(customer);
        
        if (!validationResult.IsValid)
        {
            // エラーをモデルステートに追加
            foreach (var error in validationResult.Errors)
            {
                ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
            }
            
            return BadRequest(ModelState);
        }
        
        // バリデーション成功時の処理
        // ここで顧客をデータベースに保存等
        
        return Ok(new { message = "顧客が正常に作成されました", customer });
    }
    
    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateCustomer(int id, [FromBody] Customer customer)
    {
        // 特定のルールセットのみ実行
        var validationResult = await _customerValidator.ValidateAsync(customer, options =>
        {
            options.IncludeRuleSets("UpdateValidation");
            options.IncludeProperties(x => x.Surname, x => x.Forename);
        });
        
        if (!validationResult.IsValid)
        {
            return BadRequest(validationResult.Errors.Select(e => new
            {
                Property = e.PropertyName,
                Error = e.ErrorMessage,
                AttemptedValue = e.AttemptedValue
            }));
        }
        
        return Ok();
    }
}

// Blazorでの使用例
@page "/customer-form"
@using FluentValidation
@inject IValidator<Customer> CustomerValidator

<EditForm Model="@customer" OnValidSubmit="@HandleValidSubmit">
    <FluentValidationValidator />
    <ValidationSummary />
    
    <div class="form-group">
        <label for="surname">苗字:</label>
        <InputText id="surname" @bind-Value="customer.Surname" class="form-control" />
        <ValidationMessage For="@(() => customer.Surname)" />
    </div>
    
    <div class="form-group">
        <label for="forename">名前:</label>
        <InputText id="forename" @bind-Value="customer.Forename" class="form-control" />
        <ValidationMessage For="@(() => customer.Forename)" />
    </div>
    
    <button type="submit" class="btn btn-primary">保存</button>
</EditForm>

@code {
    private Customer customer = new Customer();
    
    private async Task HandleValidSubmit()
    {
        var validationResult = await CustomerValidator.ValidateAsync(customer);
        
        if (validationResult.IsValid)
        {
            // 保存処理
            Console.WriteLine("顧客情報が保存されました");
        }
    }
}

// Entity Frameworkとの統合例
public class CustomerService
{
    private readonly IValidator<Customer> _validator;
    private readonly ApplicationDbContext _context;
    
    public CustomerService(IValidator<Customer> validator, ApplicationDbContext context)
    {
        _validator = validator;
        _context = context;
    }
    
    public async Task<Result<Customer>> CreateCustomerAsync(Customer customer)
    {
        // バリデーション実行
        var validationResult = await _validator.ValidateAsync(customer);
        
        if (!validationResult.IsValid)
        {
            return Result<Customer>.Failure(validationResult.Errors.Select(e => e.ErrorMessage));
        }
        
        // データベース保存
        try
        {
            _context.Customers.Add(customer);
            await _context.SaveChangesAsync();
            
            return Result<Customer>.Success(customer);
        }
        catch (Exception ex)
        {
            return Result<Customer>.Failure($"データベースエラー: {ex.Message}");
        }
    }
}

// 結果クラス
public class Result<T>
{
    public bool IsSuccess { get; set; }
    public T Data { get; set; }
    public IEnumerable<string> Errors { get; set; }
    
    public static Result<T> Success(T data) => new Result<T> 
    { 
        IsSuccess = true, 
        Data = data, 
        Errors = Enumerable.Empty<string>() 
    };
    
    public static Result<T> Failure(IEnumerable<string> errors) => new Result<T> 
    { 
        IsSuccess = false, 
        Errors = errors 
    };
    
    public static Result<T> Failure(string error) => Failure(new[] { error });
}

エラーハンドリングとカスタムエラーメッセージ

using FluentValidation;
using FluentValidation.Results;

// 詳細なエラーハンドリング例
public class AdvancedCustomerValidator : AbstractValidator<Customer>
{
    public AdvancedCustomerValidator()
    {
        // カスタムエラーメッセージ
        RuleFor(customer => customer.Surname)
            .NotEmpty()
            .WithMessage("苗字は必須項目です")
            .WithErrorCode("SURNAME_REQUIRED")
            .WithSeverity(Severity.Error);
        
        RuleFor(customer => customer.Forename)
            .NotEmpty()
            .WithMessage("名前は必須項目です")
            .Length(2, 50)
            .WithMessage("名前は{MinLength}文字以上{MaxLength}文字以下で入力してください")
            .WithErrorCode("FORENAME_LENGTH");
        
        // 動的エラーメッセージ
        RuleFor(customer => customer.Discount)
            .Must((customer, discount) => ValidateDiscount(customer, discount))
            .WithMessage(customer => $"顧客タイプ'{customer.GetType().Name}'では割引率{customer.Discount}%は無効です")
            .WithErrorCode("INVALID_DISCOUNT");
        
        // 複数条件のエラーメッセージ
        RuleFor(customer => customer.Address)
            .NotEmpty()
            .When(customer => customer.HasDiscount)
            .WithMessage("割引適用には住所の入力が必要です")
            .WithState(customer => new { CustomerType = "Premium", RequiresAddress = true });
        
        // カスタムステート付きバリデーション
        RuleFor(customer => customer.Postcode)
            .Must(BeValidPostcode)
            .WithMessage("郵便番号の形式が正しくありません")
            .WithState(customer => new ValidationContext
            {
                Timestamp = DateTime.Now,
                UserId = customer.Id,
                ValidationRule = "PostcodeFormat"
            });
    }
    
    private bool ValidateDiscount(Customer customer, decimal discount)
    {
        return discount >= 0 && discount <= 0.5m; // 最大50%割引
    }
    
    private bool BeValidPostcode(string postcode)
    {
        return !string.IsNullOrEmpty(postcode) && 
               System.Text.RegularExpressions.Regex.IsMatch(postcode, @"^\d{3}-\d{4}$");
    }
}

// バリデーション結果の詳細処理
public class ValidationResultProcessor
{
    public void ProcessValidationResult(ValidationResult result)
    {
        if (result.IsValid)
        {
            Console.WriteLine("バリデーション成功");
            return;
        }
        
        Console.WriteLine($"バリデーションエラー数: {result.Errors.Count}");
        
        // エラーの分類処理
        var errorsByProperty = result.Errors.GroupBy(e => e.PropertyName);
        
        foreach (var propertyErrors in errorsByProperty)
        {
            Console.WriteLine($"\nプロパティ: {propertyErrors.Key}");
            
            foreach (var error in propertyErrors)
            {
                Console.WriteLine($"  エラーコード: {error.ErrorCode}");
                Console.WriteLine($"  メッセージ: {error.ErrorMessage}");
                Console.WriteLine($"  入力値: {error.AttemptedValue}");
                Console.WriteLine($"  重要度: {error.Severity}");
                
                if (error.CustomState != null)
                {
                    Console.WriteLine($"  カスタムステート: {error.CustomState}");
                }
                Console.WriteLine();
            }
        }
    }
    
    public Dictionary<string, List<string>> GetErrorDictionary(ValidationResult result)
    {
        return result.Errors
            .GroupBy(e => e.PropertyName)
            .ToDictionary(
                g => g.Key,
                g => g.Select(e => e.ErrorMessage).ToList()
            );
    }
    
    public bool HasCriticalErrors(ValidationResult result)
    {
        return result.Errors.Any(e => e.Severity == Severity.Error);
    }
}

// カスタムバリデーションコンテキスト
public class ValidationContext
{
    public DateTime Timestamp { get; set; }
    public int UserId { get; set; }
    public string ValidationRule { get; set; }
    
    public override string ToString()
    {
        return $"検証時刻: {Timestamp:yyyy-MM-dd HH:mm:ss}, " +
               $"ユーザーID: {UserId}, " +
               $"ルール: {ValidationRule}";
    }
}

// グローバルバリデーション設定
public static class ValidationConfiguration
{
    public static void ConfigureGlobalSettings()
    {
        // グローバルカスケードモードの設定
        ValidatorOptions.Global.DefaultClassLevelCascadeMode = CascadeMode.Continue;
        ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;
        
        // デフォルト重要度の設定
        ValidatorOptions.Global.Severity = Severity.Error;
        
        // カスタム表示名解決の設定
        ValidatorOptions.Global.DisplayNameResolver = (type, member, expression) =>
        {
            if (member != null)
            {
                // プロパティ名の日本語化
                return member.Name switch
                {
                    "Surname" => "苗字",
                    "Forename" => "名前",
                    "Address" => "住所",
                    "Postcode" => "郵便番号",
                    "Discount" => "割引率",
                    _ => member.Name
                };
            }
            return null;
        };
        
        // カスタムプロパティ名分割の設定
        ValidatorOptions.Global.PropertyNameResolver = (type, member, expression) =>
        {
            if (expression != null)
            {
                // ネストしたプロパティの日本語表示
                return expression.ToString().Replace(".", " の ");
            }
            return member?.Name;
        };
    }
}

// 使用例
class Program
{
    static void Main()
    {
        // グローバル設定の適用
        ValidationConfiguration.ConfigureGlobalSettings();
        
        var customer = new Customer
        {
            Id = 1,
            Surname = "",
            Forename = "A",
            Discount = 0.6m,
            HasDiscount = true,
            Address = "",
            Postcode = "invalid"
        };
        
        var validator = new AdvancedCustomerValidator();
        var result = validator.Validate(customer);
        
        var processor = new ValidationResultProcessor();
        processor.ProcessValidationResult(result);
        
        // エラー辞書の取得
        var errorDict = processor.GetErrorDictionary(result);
        Console.WriteLine("\nエラー辞書:");
        foreach (var kvp in errorDict)
        {
            Console.WriteLine($"{kvp.Key}: {string.Join(", ", kvp.Value)}");
        }
        
        // 重大エラーの確認
        bool hasCriticalErrors = processor.HasCriticalErrors(result);
        Console.WriteLine($"\n重大エラーあり: {hasCriticalErrors}");
    }
}

型安全性とパフォーマンス最適化

using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;

// 高性能バリデータープール
public class ValidatorPool<T> where T : class
{
    private readonly ConcurrentBag<IValidator<T>> _validators;
    private readonly Func<IValidator<T>> _validatorFactory;
    
    public ValidatorPool(Func<IValidator<T>> validatorFactory)
    {
        _validatorFactory = validatorFactory;
        _validators = new ConcurrentBag<IValidator<T>>();
    }
    
    public IValidator<T> Get()
    {
        if (_validators.TryTake(out var validator))
        {
            return validator;
        }
        
        return _validatorFactory();
    }
    
    public void Return(IValidator<T> validator)
    {
        _validators.Add(validator);
    }
}

// 型安全なバリデーションサービス
public interface IValidationService
{
    Task<ValidationResult<T>> ValidateAsync<T>(T instance) where T : class;
    ValidationResult<T> Validate<T>(T instance) where T : class;
}

public class ValidationService : IValidationService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ConcurrentDictionary<Type, object> _validatorCache;
    
    public ValidationService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        _validatorCache = new ConcurrentDictionary<Type, object>();
    }
    
    public async Task<ValidationResult<T>> ValidateAsync<T>(T instance) where T : class
    {
        var validator = GetValidator<T>();
        var result = await validator.ValidateAsync(instance);
        
        return new ValidationResult<T>
        {
            Instance = instance,
            IsValid = result.IsValid,
            Errors = result.Errors.Select(e => new ValidationError
            {
                PropertyName = e.PropertyName,
                ErrorMessage = e.ErrorMessage,
                AttemptedValue = e.AttemptedValue,
                ErrorCode = e.ErrorCode,
                Severity = e.Severity
            }).ToList()
        };
    }
    
    public ValidationResult<T> Validate<T>(T instance) where T : class
    {
        var validator = GetValidator<T>();
        var result = validator.Validate(instance);
        
        return new ValidationResult<T>
        {
            Instance = instance,
            IsValid = result.IsValid,
            Errors = result.Errors.Select(e => new ValidationError
            {
                PropertyName = e.PropertyName,
                ErrorMessage = e.ErrorMessage,
                AttemptedValue = e.AttemptedValue,
                ErrorCode = e.ErrorCode,
                Severity = e.Severity
            }).ToList()
        };
    }
    
    private IValidator<T> GetValidator<T>() where T : class
    {
        return (IValidator<T>)_validatorCache.GetOrAdd(typeof(T), _ => 
            _serviceProvider.GetRequiredService<IValidator<T>>());
    }
}

// 型安全なバリデーション結果
public class ValidationResult<T> where T : class
{
    public T Instance { get; set; }
    public bool IsValid { get; set; }
    public List<ValidationError> Errors { get; set; } = new();
    
    public bool HasErrorsFor<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
    {
        var propertyName = GetPropertyName(propertyExpression);
        return Errors.Any(e => e.PropertyName == propertyName);
    }
    
    public IEnumerable<ValidationError> GetErrorsFor<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
    {
        var propertyName = GetPropertyName(propertyExpression);
        return Errors.Where(e => e.PropertyName == propertyName);
    }
    
    private string GetPropertyName<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
    {
        if (propertyExpression.Body is MemberExpression memberExpression)
        {
            return memberExpression.Member.Name;
        }
        
        throw new ArgumentException("Expression must be a member expression", nameof(propertyExpression));
    }
}

public class ValidationError
{
    public string PropertyName { get; set; }
    public string ErrorMessage { get; set; }
    public object AttemptedValue { get; set; }
    public string ErrorCode { get; set; }
    public Severity Severity { get; set; }
}

// パフォーマンス最適化されたバリデーター基底クラス
public abstract class PerformantValidator<T> : AbstractValidator<T>
{
    protected PerformantValidator()
    {
        // クラスレベルでのカスケード継続(全ルール実行)
        ClassLevelCascadeMode = CascadeMode.Continue;
        
        // ルールレベルでの即座停止(個別ルール内では最初の失敗で停止)
        RuleLevelCascadeMode = CascadeMode.Stop;
    }
    
    // 条件付きバリデーションの最適化
    protected void WhenNotNull<TProperty>(Expression<Func<T, TProperty>> expression, Action action)
        where TProperty : class
    {
        When(instance => expression.Compile()(instance) != null, action);
    }
    
    // 条件付きバリデーションの最適化(値型用)
    protected void WhenHasValue<TProperty>(Expression<Func<T, TProperty?>> expression, Action action)
        where TProperty : struct
    {
        When(instance => expression.Compile()(instance).HasValue, action);
    }
}

// 使用例:最適化されたカスタマーバリデーター
public class OptimizedCustomerValidator : PerformantValidator<Customer>
{
    public OptimizedCustomerValidator()
    {
        // 基本ルール(高速)
        RuleFor(x => x.Surname).NotEmpty().WithErrorCode("SURNAME_REQUIRED");
        RuleFor(x => x.Forename).NotEmpty().WithErrorCode("FORENAME_REQUIRED");
        
        // 条件付きルール(最適化済み)
        WhenNotNull(x => x.Address, () =>
        {
            RuleFor(x => x.Address).Length(10, 200);
        });
        
        // 複雑なルール(必要時のみ)
        RuleFor(x => x.Postcode)
            .Must(BeValidPostcode)
            .When(x => !string.IsNullOrEmpty(x.Postcode))
            .WithErrorCode("INVALID_POSTCODE");
    }
    
    private bool BeValidPostcode(string postcode) =>
        System.Text.RegularExpressions.Regex.IsMatch(postcode ?? "", @"^\d{3}-\d{4}$");
}

// ベンチマークテスト
public class ValidationBenchmark
{
    private readonly Customer _customer;
    private readonly IValidator<Customer> _validator;
    private readonly ValidationService _validationService;
    
    public ValidationBenchmark()
    {
        _customer = new Customer
        {
            Surname = "田中",
            Forename = "太郎",
            Address = "東京都新宿区新宿1-1-1",
            Postcode = "160-0022"
        };
        
        _validator = new OptimizedCustomerValidator();
        _validationService = new ValidationService(CreateServiceProvider());
    }
    
    public void BenchmarkDirectValidation()
    {
        var stopwatch = Stopwatch.StartNew();
        
        for (int i = 0; i < 10000; i++)
        {
            var result = _validator.Validate(_customer);
        }
        
        stopwatch.Stop();
        Console.WriteLine($"直接バリデーション (10,000回): {stopwatch.ElapsedMilliseconds}ms");
    }
    
    public async Task BenchmarkServiceValidation()
    {
        var stopwatch = Stopwatch.StartNew();
        
        for (int i = 0; i < 10000; i++)
        {
            var result = await _validationService.ValidateAsync(_customer);
        }
        
        stopwatch.Stop();
        Console.WriteLine($"サービス経由バリデーション (10,000回): {stopwatch.ElapsedMilliseconds}ms");
    }
    
    private IServiceProvider CreateServiceProvider()
    {
        var services = new ServiceCollection();
        services.AddScoped<IValidator<Customer>, OptimizedCustomerValidator>();
        services.AddScoped<IValidationService, ValidationService>();
        return services.BuildServiceProvider();
    }
}

// 使用例
class Program
{
    static async Task Main()
    {
        // パフォーマンステスト
        var benchmark = new ValidationBenchmark();
        benchmark.BenchmarkDirectValidation();
        await benchmark.BenchmarkServiceValidation();
        
        // 型安全なバリデーション使用例
        var services = new ServiceCollection();
        services.AddScoped<IValidator<Customer>, OptimizedCustomerValidator>();
        services.AddScoped<IValidationService, ValidationService>();
        var serviceProvider = services.BuildServiceProvider();
        
        var validationService = serviceProvider.GetService<IValidationService>();
        var customer = new Customer { Surname = "", Forename = "太郎" };
        
        var result = await validationService.ValidateAsync(customer);
        
        if (!result.IsValid)
        {
            Console.WriteLine("バリデーションエラー:");
            foreach (var error in result.Errors)
            {
                Console.WriteLine($"- {error.PropertyName}: {error.ErrorMessage}");
            }
        }
        
        // 特定プロパティのエラー確認
        if (result.HasErrorsFor(x => x.Surname))
        {
            var surnameErrors = result.GetErrorsFor(x => x.Surname);
            Console.WriteLine($"苗字のエラー数: {surnameErrors.Count()}");
        }
    }
}