티스토리 뷰
C# - Class vs Struct
구조적 차이점
“값 복사 비용이 크면 struct 대신 class로 바꾸면 되지 않나?”
“in 키워드로 참조 전달이면 struct도 사실상 class 아닌가?”
C#에서는 Class와 Struct가 겉보기엔 비슷하게 보이지만, 그 내부 동작과 철학은 완전히 다르다.
일반적으로는 값 전달 방식의 차이와 복사값의 차이(class는 얕은 복사(주소), struct는 깊은 복사(값) 발생)가 있다.
in struct는 단순한 최적화가 아니라, “값형의 의미를 보존하면서 복사를 회피” 하는 언어적 장치이다. 이 차이를 이해하면 성능과 설계 안정성 모두를 잡을 수 있다.
| 항목 | Class | Struct |
| 저장 위치 | 힙(Heap) | 스택(Stack) 또는 인라인 |
| 전달 방식 | 참조 전달 | 값 전달(복사) |
| GC 대상 | ✅ 수거됨 | ❌ 스택 종료 시 소멸 |
| 기본 동등성 | 참조 동일성 | 값 비교 |
| 불변성 보장 | ❌ 명시적 설계 필요 | ✅ readonly struct 가능 |
| 박싱(Heap 변환) | 없음 | 있음 (object 캐스팅 시) |
| 상속 | 가능 | 불가능 (인터페이스만 구현 가능) |
| 생성자 | 파라미터 생성자만 | 파라미터/기본 생성자 지원 |
| 메모리 연속성 | 비연속적 | 연속적 (캐시 친화적) |
더보기
1. Class는 "공유와 상태 변화"를 위한 구조이고, Struct는 "값과 데이터의 불변성"을 위한 구조다.
2. 연속 메모리는 캐시 적중률을 증가시킨다.
예시
class PointClass {
public float X;
public float Y;
}
struct PointStruct {
public float X;
public float Y;
}
void Example() {
PointClass a = new PointClass { X = 1, Y = 2 };
PointStruct b = new PointStruct { X = 1, Y = 2 };
PointClass c = a; // 참조 복사
PointStruct d = b; // 값 복사
}
| 변수 | 메모리 복사 | 동일 객체 여부 |
| a vs c | ❌ (참조만 복사됨) | ✅ (같은 객체) |
| b vs d | ✅ (값 전체 복사됨) | ❌ (별개 객체) |
in struct - 참조형(Readonly) 구조체 전달
in 키워드를 사용하면 구조체도 클래스와 같이 참조 값 전달이 가능하다.
이는 큰 구조체를 함수에 전달할 때 복사 비용 부담을 줄인다.
in은 값형을 참조로 읽되, 클래스처럼 공유되지 않도록 하는 장치이다.
void Process(BigStruct data) {
/* 복사 발생 */
}
void Process(in BigStruct data) {
Console.WriteLine(data.X);
// data.X = 10; ❌ 컴파일 에러
}
in struct vs class
in 키워드를 통해 참조만 한다면 클래스 사용과 무엇이 다른가?
이는 아주 중요한 차이점을 바라보면 되는데, 바로 참조 전달이라는 겉모습만 같을 뿐, struct은 "값을 다루는 방식"이고 class는 "객체를 다루는 방식"인 것이다.
대량의 작은 데이터 구조체 = struct + in가 유리, 독립적 로직과 수명 관리 = class가 유리
| 항목 | struct | class |
| 생성/삭제 | 스택, 빠름 | 힙, 느림 |
| GC | 없음 | 있음 |
| 캐시 지역성 | ✅ 좋음 | ❌ 나쁨 |
| 동시성 안정성 | ✅ 불변 설계 용이 | ❌ 공유 데이터 경쟁 |
| 함수 전달 | 복사 발생 (단, in으로 완화 가능) | 참조 전달 (복사 없음) |
| 설계 철학 |
데이터 중심 | 행위 중심 |
실사용 가이드
| 가정 | 구조 | 이유 |
| 구조체 ≈16B 이하 (Matrix, Transform, Color 등) | readonly struct + in | 복사비용 절감 + 불변성 |
| 수명이 짧은 데이터 (좌표, 계산용 임시 변수 등) | struct | 스택 기반 성능 우수 |
| 상태가 지속되는 객체 (Player, Enemy, Manager 등) | class | 참조 공유가 본질 |
| 다수의 동일 타입 데이터를 배열로 다룸 | struct | 메모리 연속성 최고 |
| 인터페이스 다형성, 상속 필요 | class | 구조체는 상속 불가 |
- 작고(≈16B 이하) 읽기 위주이며 대량으로 순회 → struct + 연속 메모리(List<T>, 배열) 매우 효율적
- 자주 삽입/삭제/정렬로 요소를 많이 “이동/복사” 혹은 대용량 구조체(≥64B) → class
- 엔진/런타임(Job/Burst, IL2CPP, GC 민감) → struct 권장 (힙 할당/GC 회피)
- 상태 공유/동기화가 본질 → class
크기 기준
* (프로젝트 / 용도 / 플렛폼 등에 따라 기준이 달라질 수 있음)
- 작음(≤16B)
- struct
- Ex) 2~4개의 float/int
- 중간(16~64B)
- 사용 패턴으로 결정
- 순회 위주/읽기 위주 = struct (+ in 전달)
- 빈번한 재배치/정렬/삽입/삭제 = class
- 큼(≥64B)
- 컬렉션에서 자주 이동되면 class
- 고정 레이아웃 + 순회 전용이면 struct 유지
선형 컨테이너 사용 예시
List<struct>
- 저장 형태
- 요소가 배열에 인라인(연속) 저장 → 캐시 적중률 상승
- 순회 성능 좋음.
- 단점
- 요소 이동/복사 비용이 요소 크기에 비례. Insert, RemoveAt, Sort 등에서 큰 struct일수록 비용 상승
- list[i]로 꺼낸 struct는 복사본이기에 필드 수정해도 원본에 반영되지 않음
- 다시로 덮어쓰는 프로세스 필요
- 인터페이스/비제네릭 컬렉션으로 다루면 박싱 발생 가능.
var v = list[i]; // 값 복사 v.X += 1; // 복사본 수정 list[i] = v; // 다시 써 넣어야 반영
List<class>
- 저장 형태
- 참조(포인터) 배열 → 요소 이동/정렬 시 참조만 스왑
- 대형 객체도 복사비용 거의 없음
- 단점
- 각 요소는 힙 할당 + GC 대상
- 포인터 추적으로 캐시 지역성 하락
- 대량 생성/파괴 시 GC 압력과 프래그먼테이션 리스크 존재
실무 판단
- 순회가 압도적으로 많고, 요소가 작다(예: Vector2 8B, Vector3 12B, Color32 4B 등) = List<struct>
- 자주 삽입/삭제/정렬 + 큰 데이터(예: 64B+) = List<class>
- Unity Burst/Jobs/NativeArray 사용 → struct 고정
'Computer Language > C#' 카테고리의 다른 글
| [C#] 읽기 전용 참조 전달 (in) (0) | 2025.11.09 |
|---|---|
| [C#] 박싱과 언박싱 (Boxing & Unboxing) (1) | 2024.03.27 |
| [C#] 다중조건문의 동작 순서 (0) | 2023.08.15 |
| [C#] 메모리 할당 (0) | 2023.08.15 |
| [C#] StringBuilder (0) | 2023.08.15 |