C#의 모든 형식은 크게 값 형식(Value Type)과 참조 형식(Reference Type)이라는 두 가지 범주로 나눌 수 있습니다. 이 두 형식은 메모리에서 데이터를 저장하고 처리하는 방식이 근본적으로 다르며, 이 차이를 이해하는 것은 C# 프로그래밍의 기초 중 하나입니다. 지금부터 각 형식을 자세히 살펴보겠습니다.
값 형식(Value Type)이란?
값 형식 변수는 데이터를 직접 저장합니다. 각 변수는 자신만의 독립적인 데이터 복사본을 가지고 있기 때문에, 한 값 형식 변수가 다른 객체의 값을 수정할 수 없습니다.
값 형식에는 다음과 같은 종류가 있습니다.
- 모든 숫자 형식 — 예: int, float, double
- char 및 bool 형식
- struct(구조체) 형식
- enum(열거형) 형식
값 형식은 이름 그대로 '값' 자체를 담고 있습니다. 예를 들어 정수형 변수에는 실제 숫자가 저장되며, 참조 형식처럼 해당 숫자를 가리키는 포인터가 저장되는 것이 아닙니다.
struct로 사용자 정의 값 형식 만들기
사용자 정의 값 형식을 만들려면 구조체(struct)를 사용하면 됩니다.
public struct Point
{
public int X;
public int Y;
}
var p1 = new Point();메모리상에서 Point 인스턴스는 아래 그림처럼 표현됩니다.

값 형식의 대입 동작: 복사 발생
값 형식 변수를 다른 변수에 대입하면, 대입 연산은 원래 값을 그대로 복사합니다.
Point p2 = p1;

즉, p1과 p2는 서로 완전히 독립된 두 개의 데이터를 가지게 됩니다.
참조 형식(Reference Type)이란?
참조 형식 변수는 객체 자체가 아니라 객체에 대한 참조(주소)를 저장합니다. 따라서 서로 다른 두 변수가 같은 객체를 참조할 수 있으며, 한 변수가 객체를 변경하면 그 변경 사항이 다른 변수에도 그대로 반영됩니다.
참조 형식에는 다음과 같은 종류가 있습니다.
- String(문자열)
- Class(클래스)
- Array(배열)
- Delegate(대리자)
- Interface(인터페이스)
클래스로 참조 형식 만들기
다음과 같이 클래스를 정의하면 참조 형식이 됩니다.
public class Point
{
public int X;
public int Y;
}
var p1 = new Point();
참조 형식의 대입 동작: 참조만 복사
참조 형식 변수를 다른 변수에 대입하면, 실제 객체가 아니라 참조만 복사됩니다.
Point p2 = p1;

결과적으로 p1과 p2는 동일한 객체를 가리키게 됩니다.
실습 예제: 값 형식 vs 참조 형식
두 형식의 차이를 확인할 수 있는 전체 예제 코드입니다.
using System;
class Program
{
static void Main()
{
var p1 = new Point { X = 10 };
Point p2 = p1;
p2.X = 20;
Console.WriteLine("Value Type");
Console.WriteLine(p1.X);
Console.WriteLine(p2.X);
var u1 = new User { Age = 10 };
User u2 = u1;
u2.Age = 20;
Console.WriteLine("Reference Type");
Console.WriteLine(u1.Age);
Console.WriteLine(u2.Age);
}
}
public struct Point
{
public int X;
public int Y;
}
public class User
{
public int Age { get; set; }
}실행 결과
Value Type 10 20 Reference Type 20 20
결과 해석
출력 결과를 보면 두 형식의 차이가 명확하게 드러납니다.
- 값 형식(Point 구조체): p2.X를 20으로 변경해도 p1.X는 여전히 10입니다. 대입 시 값이 복사되어 서로 독립적이기 때문입니다.
- 참조 형식(User 클래스): u2.Age를 20으로 변경하면 u1.Age도 20으로 바뀝니다. 두 변수가 같은 객체를 공유하기 때문입니다.
이처럼 값 형식과 참조 형식의 동작 방식을 정확히 이해하면, 의도치 않은 버그를 예방하고 더 안전하고 효율적인 C# 코드를 작성할 수 있습니다.