C#는 기본적으로 값 전달을 합니다. ref, out, in 키워드를 사용하면 매개변수를 참조로 전달해 복사 비용을 줄이거나 호출자 변수에 영향을 줄 수 있습니다. 각 키워드의 의도와 제약을 이해하면 안전하고 빠른 코드를 작성할 수 있습니다.
1. 값 전달 vs 참조 전달
값 전달: 매개변수 복사본을 받습니다. 메서드 내부 변경이 호출자 변수에 영향을 주지 않습니다.
참조 전달(ref/out/in): 원본에 대한 참조를 받습니다. 상황에 따라 읽기/쓰기 가능 범위가 다릅니다.
2. ref: 읽고 쓰는 참조 전달
ref는 호출자 변수의 현재 값을 읽고, 수정 결과를 다시 돌려줍니다. 호출 전 변수는 반드시 할당되어 있어야 합니다.
static void Swap(ref int a, ref int b)
{
int t = a;
a = b;
b = t;
}
int x = 1, y = 2;
Swap(ref x, ref y); // x=2, y=1참조 형식에도 적용 가능합니다. ref를 쓰면 "객체 자체"가 아니라 "참조(변수)가 가리키는 대상"을 바꿀 수 있습니다.
using System.Text;
static void Rebind(ref StringBuilder sb)
{
sb = new StringBuilder("new");
}
var sb1 = new StringBuilder("old");
Rebind(ref sb1); // sb1은 이제 "new"를 가리킵니다.3. out: 출력 전용 참조 전달
out은 메서드가 값을 초기화해 돌려주는 용도입니다. 호출 전 변수는 초기화되지 않아도 되지만, 메서드 내부에서 반환 전에 반드시 할당해야 합니다.
// 대표 패턴: TryParse
if (int.TryParse("123", out var value))
{
Console.WriteLine(value); // 123
}
else
{
Console.WriteLine("숫자가 아님");
}out var로 호출 지점에서 바로 변수 선언이 가능합니다. 여러 값을 돌려줘야 할 때도 out을 사용할 수 있으나, 최근에는 튜플/record 반환이 더 선호됩니다.
4. in: 읽기 전용 참조 전달 (C# 7.2+)
in은 값을 복사하지 않고 읽기 전용으로 전달합니다. 주로 "큰 값 형식(struct)"에서 복사 비용을 줄이는 데 유용합니다. 메서드 안에서 in 파라미터를 수정할 수 없습니다.
public readonly struct Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y) { X = x; Y = y; }
public readonly int LengthSquared() => X * X + Y * Y;
}
static void PrintLength(in Point p)
{
Console.WriteLine(p.LengthSquared());
// p.X = 10; // 컴파일 오류: in 매개변수는 읽기 전용
}
var p = new Point(3, 4);
PrintLength(in p); // 복사 없이 읽기 전용 참조 전달주의: 구조체의 비-readonly 멤버를 호출하면 컴파일러가 방어적 복사를 만들 수 있습니다. readonly struct/readonly 멤버를 사용하면 복사를 피하는 데 유리합니다. 참조 형식(class)에는 in을 써도 이점이 없습니다(참조 자체만 읽기 전용이 되며 객체는 여전히 변경 가능).
5. 무엇을 언제 쓸까
- ref: 호출자 변수 자체를 바꿔야 하거나, 값 형식 대입 비용을 피하면서 읽고 쓰기가 모두 필요할 때 사용합니다.
- out: 실패할 수 있는 초기화/파싱/Try-패턴 등 "메서드가 값을 공급"해야 할 때 사용합니다.
- in: 큰 struct를 읽기 전용으로 전달해 복사를 피하고 싶을 때 사용합니다(C# 7.2+). 구조체가 충분히 크고 읽기 전용 멤버로 구성되었을 때 효과적입니다.
6. 자주 하는 실수와 제약
- 호출 시 키워드 필요: ref/out/in을 파라미터뿐 아니라 호출 지점에서도 붙여야 합니다.
- 전달 가능한 식: 변수, 배열 요소, ref 반환 등 "변수 위치"만 가능합니다. 일반 속성/식 결과는 ref/out/in으로 전달할 수 없습니다.
- async/iterator 제약: async/await 메서드와 iterator(yield) 메서드는 ref/out/in 매개변수를 가질 수 없습니다.
- params/기본값과 혼용 불가: ref/out/in 매개변수는 기본값이나 params와 함께 사용할 수 없습니다.
- 오버로드 구분: by-value와 in 오버로드가 동시에 있을 때, in을 명시해야 올바른 메서드가 선택됩니다.
7. 요약
ref는 읽기/쓰기, out은 쓰기 전용, in은 읽기 전용 참조 전달입니다. 값 복사를 줄여 성능을 개선할 수 있지만, 가독성과 제약을 함께 고려해야 합니다. 큰 struct에는 in, Try-패턴에는 out, 변수 재바인딩이나 양방향 수정에는 ref를 선택합니다.
'C#' 카테고리의 다른 글
| C# 접근 제한자 (Access Modifiers) (0) | 2026.04.15 |
|---|---|
| C# 생성자와 소멸자 (0) | 2026.04.15 |
| C# static 키워드 완벽 이해 (1) | 2026.04.13 |
| C# 컬렉션 초기화와 Index/Range (1) | 2026.04.13 |
| C# 애트리뷰트 (Attribute) 정의와 활용 (0) | 2026.04.10 |