C# Discriminated Unions 패턴
Discriminated Union이란
섹션 제목: “Discriminated Union이란”Discriminated Union(판별 합집합)은 여러 타입 중 정확히 하나가 될 수 있는 타입입니다. F#, Rust, Haskell 등 함수형 언어에서 핵심 기능이지만, C#은 공식 지원이 없어 패턴으로 구현합니다.
sealed 계층 + switch 패턴
섹션 제목: “sealed 계층 + switch 패턴”// 기반 타입을 sealed abstract로 정의public abstract class Result { private Result() {} // 외부 상속 차단
public sealed class Ok : Result { public string Value { get; } public Ok(string value) => Value = value; }
public sealed class Error : Result { public string Message { get; } public Error(string message) => Message = message; }}
// 사용Result result = new Result.Ok("성공");
string output = result switch { Result.Ok ok => $"성공: {ok.Value}", Result.Error error => $"실패: {error.Message}", _ => throw new UnreachableException()};record 기반 DU (C# 9+)
섹션 제목: “record 기반 DU (C# 9+)”public abstract record Shape;public record Circle(double Radius) : Shape;public record Rectangle(double Width, double Height) : Shape;public record Triangle(double Base, double Height) : Shape;
double Area(Shape shape) => shape switch { Circle c => Math.PI * c.Radius * c.Radius, Rectangle r => r.Width * r.Height, Triangle t => 0.5 * t.Base * t.Height, _ => throw new ArgumentOutOfRangeException()};record는 구조적 동등성과 with 복사를 자동 제공합니다.
완전성 검사 강제
섹션 제목: “완전성 검사 강제”C# 컴파일러는 아직 완전성 검사(exhaustiveness check)를 강제하지 않습니다. _ 패턴에서 예외를 던지는 방식으로 런타임 안전성을 확보합니다.
// 새 타입 추가 시 switch에서 누락되면 런타임 예외public record Pentagon : Shape; // 추가
// 아래 switch는 런타임에 UnreachableException 발생double area = new Pentagon() switch { Circle c => ..., Rectangle r => ..., Triangle t => ..., _ => throw new UnreachableException($"처리 안 된 Shape: {shape.GetType()}")};Option/Maybe 패턴
섹션 제목: “Option/Maybe 패턴”public abstract record Option<T> { public record Some(T Value) : Option<T>; public record None : Option<T>;
public static Option<T> Of(T? value) => value is null ? new None() : new Some(value);
public TResult Match<TResult>(Func<T, TResult> onSome, Func<TResult> onNone) => this switch { Some s => onSome(s.Value), None => onNone(), _ => throw new UnreachableException() };}
Option<string> name = Option<string>.Of(GetName());string display = name.Match(n => $"안녕, {n}!", () => "이름 없음");Result 모나드 패턴
섹션 제목: “Result 모나드 패턴”public abstract record Result<T> { public record Ok(T Value) : Result<T>; public record Err(string Message) : Result<T>;
public Result<U> Map<U>(Func<T, U> f) => this switch { Ok ok => new Result<U>.Ok(f(ok.Value)), Err e => new Result<U>.Err(e.Message), _ => throw new UnreachableException() };
public Result<U> FlatMap<U>(Func<T, Result<U>> f) => this switch { Ok ok => f(ok.Value), Err e => new Result<U>.Err(e.Message), _ => throw new UnreachableException() };}OneOf 라이브러리
섹션 제목: “OneOf 라이브러리”NuGet의 OneOf 패키지를 사용하면 더 간결하게 구현됩니다.
using OneOf;
OneOf<int, string, bool> val = 42;
string result = val.Match( i => $"정수: {i}", s => $"문자열: {s}", b => $"불린: {b}");sealed abstract class/record+private생성자로 외부 상속 차단- C# 9
record타입으로 간결한 DU 계층 구성 switch표현식으로 패턴 매칭,_에서 예외로 완전성 보장Option<T>,Result<T>패턴으로 null/예외를 타입으로 표현- C# 향후 버전에서 native DU 문법 논의 중