Quick Reference
값 타입 변수는 값 인스턴스를, 참조 타입 변수는 객체를 가리키는 참조를 담습니다. 기본 대입·인자 전달·반환은 모두 변수 값을 복사하므로, 값 타입에서는 인스턴스가 복사되고 참조 타입에서는 같은 객체를 가리키는 참조가 복사됩니다.
var left = new Point(1, 2);
var right = left;
right = right with { X = 10 }; // left는 (1, 2) 그대로
var first = new Player("Mina");
var second = first;
second.Name = "Jin"; // first.Name도 "Jin"int,enum,struct,ValueTuple은 값 타입이고class,string, 배열, interface는 참조 타입입니다.- 값 타입 안에
List<T>같은 참조 멤버가 있으면 struct를 복사해도 그 멤버가 가리키는 객체는 공유됩니다. - 값 타입을
object나 구현 interface로 변환하면 boxing이 발생할 수 있습니다. - 스택·힙 위치는 JIT와 사용 맥락의 구현 세부입니다. 타입 선택은 저장 위치가 아니라 복사·identity·수명 계약으로 판단합니다.
복사와 identity
public readonly record struct Point(int X, int Y);
public sealed class Player(string name)
{
public string Name { get; set; } = name;
}값 타입은 복사 뒤에 각각 독립적인 값이 됩니다. 참조 타입은 두 변수가 같은 객체를 가리킬 수 있으므로 한 쪽의 가변 상태 변경이 다른 쪽에서 관찰됩니다. 다만 string은 참조 타입이지만 불변이어서, 결합·교체는 새 string을 가리키게 할 뿐 기존 string 객체를 바꾸지 않습니다.
string first = "hello";
string second = first;
first += " world";
Console.WriteLine(second); // hello값 타입의 복사는 얕습니다. 값 안에 참조가 있으면 그 참조 자체가 복사될 뿐, 참조 대상 객체는 복제되지 않습니다.
public struct TaggedNumber(int number, List<string> tags)
{
public int Number = number;
public List<string> Tags = tags;
}
var original = new TaggedNumber(1, new List<string> { "A" });
var copy = original;
copy.Number = 2;
copy.Tags.Add("B");
// original.Number는 1, original.Tags에는 A와 B가 모두 있습니다.Null과 boxing 경계
값 타입에는 기본적으로 null을 대입할 수 없습니다. int?처럼 Nullable<T>를 쓰면 값 또는 null을 표현합니다. 참조 타입의 null annotation과 흐름 분석은 nullable reference type에서 다룹니다.
int? score = null;
int value = score ?? 0;
int number = 42;
object boxed = number; // int -> object: boxing
int restored = (int)boxed; // 같은 실제 타입이어야 unboxing 가능object 또는 interface로의 변환이 항상 필요한 설계라면 boxing 비용과 타입 검사 실패를 고려합니다. List<int> 같은 제네릭 컨테이너는 값을 object로 보관하지 않으므로 일반적인 추가·조회에서 boxing을 피하지만, 제네릭 코드가 다시 object 또는 interface API를 호출하면 conversion은 발생할 수 있습니다.
선택 기준
| 필요 | 보통의 선택 | 확인할 경계 |
|---|---|---|
| 독립적으로 복사되는 작은 데이터 값 | immutable struct 또는 record struct | 참조 멤버가 섞이면 얕은 복사 |
| identity·공유 가변 상태·수명 관리 | class | alias가 상태 변경을 공유 |
| 값 기반 DTO·설정 | record class | with도 참조 멤버는 얕은 복사 |
| 값 또는 없음 | T? | null 외에 범위·형식 오류는 별도 검증 |
성능을 이유로 struct를 먼저 선택하지 않습니다. 실제 복사량·boxing 경로·할당과 lifetime을 측정한 뒤 결정하며, struct 자체가 stack allocation을 보장하지도 class 자체가 항상 heap allocation을 뜻하지도 않습니다.
자주 틀리는 부분
ref 매개변수는 참조 타입만 위한 기능이 아닙니다. 값 타입도 ref, in, out으로 별칭 전달할 수 있으며, 이때는 복사 규칙이 달라집니다. 자세한 alias와 escape 규칙은 ref, out, params에서 확인합니다.
참조 타입이라고 언제나 가변인 것도, 값 타입이라고 언제나 완전히 독립적인 것도 아닙니다. 실제 멤버의 불변성·참조 공유·API 입력 방식을 함께 확인해야 합니다.
참고 링크
2 sources