C# 15 유니온
C# 유니온 계획
기본 개념과 코드 예시
Pet pet = new Dog("Rover");
var description = pet switch {
Dog(var name) => name,
Cat(var adjective) => $"A {adjective} cat",
Bird bird => $"A {bird.Species}",
};
public union Pet (Dog, Cat, Bird);
public record class Dog(string Name);
public record struct Cat(string Adjective);
public record class Bird(string Species);
public record class Shark(int Teeth);
주요 특징
유니온 타입 정의
-
Pet은 Dog, Cat, Bird의 유니온으로 정의됨
-
구성 타입들을 반복적으로 지정할 필요 없음
-
Pet이 정의를 완전히 캡슐화함
암시적 변환
-
Pet pet = new Dog("Rover");
에서 컴파일러가 Dog에서 Pet으로의 암시적 변환을 인식 -
Pet은 Dog의 기본 클래스가 아님에도 변환 가능
C#과 F#의 차이점
C# 방식 (구조적 유니온/타입 유니온):
-
유니온을 구성하는 타입들을 유니온과 별도로 정의해야 함
-
각 타입이 독립적으로 존재
F# 방식 (판별 유니온/태그 유니온):
type Vehicle =
| Car of model : string * year : int
| Truck of model : string * year : int * payload : float
| Bicycle of model : string
-
유니온 내부에 케이스들을 직접 정의
-
타입보다는 태그로서 기능
패턴 매칭의 완전성 검사
-
컴파일러가 Pet이 Dog, Cat, Bird만 가능함을 인지
-
모든 케이스를 처리하면 switch 표현식에서 경고 없음
-
나중에 Shark를 추가하면 해당 케이스를 처리하지 않은 모든 switch에서 경고/오류 발생
폐쇄형 유니온 타입
-
가능성의 집합이 고정되고 컴파일러가 강제함
-
클래스 상속과 대조적 (다형성을 통해 새 서브클래스 추가 가능)
-
개방-폐쇄 원칙(OCP)이 클래스 상속에는 적용되지만 유니온에는 적용되지 않음
구현 방식
내부 구조
#region public union Pet (Dog, Cat, Bird);
public partial record struct Pet : IUnion
{
public Pet(Dog value) => Value = value;
public Pet(Cat value) => Value = value;
public Pet(Bird value) => Value = value;
public object? Value { get; }
}
#endregion
설계 특징
-
record struct로 구현
-
단일 읽기 전용
object? Value
속성 보유 -
C#에서 거의 모든 타입을 object 참조로 표현 가능
-
클래스, 구조체, 인터페이스, 델리게이트, 열거형, 원시 타입 등
-
단, 관리되지 않는 포인터는 불가
값 타입 전용 유니온 최적화
현재 구현의 트레이드오프
-
Cat과 같은 값 타입이 Value 속성에 저장될 때 박싱됨
-
박싱된 객체는 가비지 컬렉터가 관리해야 함
-
작은 성능 오버헤드 발생
잠재적 최적화 방안
-
동일한 메모리 크기를 공유하는 값 타입들의 저장소 최적화
-
유니온 내 가장 큰 값 타입 기준으로 공간 할당 (작은 타입은 패딩 오버헤드)
-
C# 팀은 값 타입 전용 유니온의 레이아웃을 수동으로 정의할 방법 제공 예정
MemoryMarshal을 활용한 바이트 캐스팅 예시
using System.Diagnostics;
using System.Runtime.InteropServices;
Span<byte> bytes8 = stackalloc byte[8] { 2, 0, 0, 0, 1, 0, 0, 0 };
// Convert first 4 bytes to uint
uint value32 = MemoryMarshal.Read<uint>(bytes8);
Debug.Assert(value32 == 2);
// Convert all 8 bytes to ulong
ulong value64 = MemoryMarshal.Read<ulong>(bytes8);
Debug.Assert(value64 == 4294967298);
// Convert all 8 bytes to MyStruct
ref MyStruct myStruct = ref MemoryMarshal.AsRef<MyStruct>(bytes8);
Debug.Assert(myStruct.X == 2);
Debug.Assert(myStruct.Y == 1);
[StructLayout(LayoutKind.Sequential)]
struct MyStruct { public uint X; public uint Y; }
패턴 매칭 switch
Value 속성을 통한 패턴 매칭
var description = pet.Value switch {
Dog(var name) => name,
Cat(var adjective) => $"A {adjective} cat",
Bird bird => $"A {bird.Species}",
};
-
컴파일러가 Value 속성 호출을 추론
-
간단하고 직관적인 구현
향후 계획 및 고려사항
릴리스 일정
-
C# 15 (2026년 11월): 가능성은 있지만 확정되지 않음
-
C# 14 GA (2025년 11월) 이후 작업 시작 예정
협업 계획
-
.NET Core Base Class Library 팀과 협력
-
언어 기능 릴리스 시 많은 유니온 타입이 이미 사용 가능하도록 준비
F# 상호 운용성
-
과제: F# 판별 유니온과 C# 타입 유니온의 근본적 차이
-
계획: 상호 운용성 모델 작업 예정
-
추가 가능성: F#처럼 유니온 범위 내에서 직접 타입 정의 허용 고려
주요 인물 및 출처
-
Mads Torgersen: C# 프로그래밍 언어 수석 디자이너
-
2025년 8월 비디오: 1:02:25부터 1:10:30까지 유니온 관련 내용
-
2024년 관련 블로그 포스트: “C# Discriminated Union: What’s Driving the C# Community’s Inquiries?”
실용적 활용 시나리오
-
파서 구현
-
메시징 프로토콜
-
값 타입만으로 구성된 유니온이 일반적인 시나리오가 될 가능성
주의사항
-
유니온은 확정된 기능이 아닌 가능성
-
구현 방식과 세부사항은 변경될 수 있음
-
OCP가 유니온에는 적용되지 않으므로 확장 시 기존 코드 수정 필요