C#에서 메서드를 호출할 때 인수를 전달하면, 기본적으로 전달한 값이 매개변수에 복사됩니다.
따라서 메서드 내부에서 매개변수의 값을 변경하더라도 원본 변수의 값은 변경되지 않습니다.
예를 들어 두 변수의 값을 서로 바꾸는 Swap() 메서드를 작성해 보겠습니다.
static void Swap(int a, int b)
{
int temp = b;
b = a;
a = temp;
}
static void Main()
{
int x = 1;
int y = 2;
Swap(x, y);
Console.WriteLine($"x = {x}, y = {y}");
}
실행 결과
x = 1, y = 2
Swap() 메서드에서는 a와 b의 값을 서로 교환했지만, 실제 x와 y의 값은 변경되지 않았습니다. 왜 그럴까요?
메서드에 전달되는 a와 b는 원본 변수인 x, y가 아니라 각각의 값을 복사해서 전달받은 별도의 변수이기 때문입니다. 따라서 Swap() 내부에서 a와 b의 값을 변경하더라도 원본 변수인 x, y에는 영향을 주지 않습니다.
그렇다면 메서드 내부에서 원본 변수의 값을 직접 변경하려면 어떻게 해야 할까요? 이때 사용할 수 있는 것이 ref와 out입니다.
두 키워드 모두 변수를 참조로 전달한다는 공통점이 있지만, 사용하는 목적에는 차이가 있습니다.
ref
ref는 메서드에 변수를 참조로 전달할 때 사용하는 키워드입니다.
일반적인 값 전달과 달리 매개변수가 호출한 쪽의 원본 변수를 참조하므로, 메서드 내부에서 값을 변경하면 원본 변수에도 반영됩니다. 앞서 작성했던 Swap() 메서드를 ref를 사용하도록 수정해 보겠습니다.
static void Swap(ref int a, ref int b)
{
int temp = b;
b = a;
a = temp;
}
static void Main()
{
int x = 1;
int y = 2;
Swap(ref x, ref y);
Console.WriteLine($"x = {x}, y = {y}");
}
실행 결과
x = 2, y = 1
이번에는 x와 y의 값이 정상적으로 교환되었습니다.
일반적인 값 전달에서는 a와 b가 각각 x, y의 값을 복사받았지만, ref를 사용하면 원본 변수 자체를 참조하게 됩니다. 따라서 a와 b를 수정하는 것은 실제로 x와 y를 수정하는 것과 같습니다.
여기서 주의할 점은 ref를 사용할 때는 메서드를 호출하기 전에 변수가 초기화되어 있어야 한다는 것입니다.
static void ChangeValue(ref int value)
{
value = 100;
}
static void Main()
{
int number;
ChangeValue(ref number); // 컴파일 오류
}
number를 선언했지만 값을 할당하지 않았기 때문에 컴파일 오류가 발생합니다.
ref는 메서드 내부에서 기존 값을 읽을 수도 있으므로, 호출하기 전에 변수를 초기화해야 합니다.
ref는 언제 사용할까?
ref는 기존 변수의 값을 이용해 계산하거나, 그 값을 직접 수정해야 할 때 사용할 수 있습니다.
대표적인 예시가 앞서 살펴본 Swap() 메서드입니다. 기존에 저장되어 있던 두 변수의 값을 읽고 서로 교환해야 하므로 ref를 사용하는 것이 적합합니다.
다만 원본 변수를 수정해야 한다고 해서 반드시 ref를 사용해야 하는 것은 아닙니다.
static int TakeDamage(int hp, int damage)
{
return hp - damage;
}
static void Main()
{
int hp = 100;
hp = TakeDamage(hp, 30);
Console.WriteLine(hp); // 70
}
이처럼 단순히 하나의 값을 계산해서 반환한다면 return을 이용하는 편이 더 간결할 수 있습니다.
따라서 ref는 무조건 사용하기보다는, 메서드에서 호출한 쪽의 변수 자체를 수정할 필요가 있을 때 사용하는 것이 좋습니다.
out
out도 ref와 마찬가지로 변수를 참조로 전달하는 키워드입니다. 하지만 ref와는 사용 목적이 조금 다릅니다.
ref는 기존 변수의 값을 읽고 수정할 때 사용한다면, out은 메서드에서 계산하거나 생성한 결과를 호출한 쪽에 전달할 때 주로 사용합니다.
간단한 예제를 살펴보겠습니다.
static void GetPlayerStats(out int hp, out int attack)
{
hp = 100;
attack = 20;
}
static void Main()
{
GetPlayerStats(out int hp, out int attack);
Console.WriteLine($"HP = {hp}");
Console.WriteLine($"Attack = {attack}");
}
실행 결과
HP = 100
Attack = 20
GetPlayerStats()에서는 out으로 전달받은 hp와 attack에 값을 할당하고 있습니다. 호출하는 쪽에서는 기존 값을 준비할 필요 없이, 메서드에서 할당한 결과를 전달받기만 하면 됩니다.
이처럼 out을 사용하면 return으로 반환하는 값과 별개로 여러 결과를 전달할 수 있습니다.
그렇다면 여기서 한 가지 의문이 생깁니다.
ref도 값을 수정할 수 있는데, 굳이 out을 사용하는 이유는 무엇일까요?
ref와 out의 차이
두 키워드의 가장 큰 차이는 변수의 초기화와 값 할당에 있습니다.
ref는 메서드를 호출하기 전에 변수가 초기화되어 있어야 합니다. 반면 out은 기존 값을 사용할 필요가 없기 때문에 초기화하지 않은 변수도 전달할 수 있습니다.
static void ChangeValue(out int value)
{
value = 100;
}
static void Main()
{
int number;
ChangeValue(out number);
Console.WriteLine(number); // 100
}
앞서 ref에서는 컴파일 오류가 발생했던 코드지만, out을 사용하면 정상적으로 실행됩니다.
대신 out에는 중요한 조건이 있습니다. 메서드가 정상적으로 종료되기 전에 반드시 out 매개변수에 값을 할당해야 합니다.
static void ChangeValue(out int value)
{
Console.WriteLine(value); // 컴파일 오류
}
위 코드에서는 value에 값을 할당하지 않은 상태에서 읽으려고 했기 때문에 컴파일 오류가 발생합니다.
out 매개변수는 호출하는 쪽에서 이미 초기화했더라도 메서드 내부에서는 할당되기 전의 값을 읽을 수 없습니다.
따라서 out은 기존 값을 활용하는 용도가 아니라, 메서드에서 새로운 값을 할당하여 결과를 전달하는 용도에 적합합니다.
out은 언제 사용할까?
대표적으로 int.TryParse()가 있습니다.
TryParse()는 문자열을 정수로 변환할 수 있는지 확인하는 메서드입니다.
static void Main()
{
string input = "150";
if (int.TryParse(input, out int number))
{
Console.WriteLine($"변환 성공 : {number}");
}
else
{
Console.WriteLine("변환 실패");
}
}
실행 결과
변환 성공 : 150
여기서 TryParse()는 두 가지 결과를 전달합니다.
- return : 문자열을 정수로 변환하는 데 성공했는지 bool 값으로 반환합니다.
- out : 변환된 정수 값을 number에 저장합니다.
즉, 변환 성공 여부는 return을 통해 확인하고, 실제 변환된 값은 out으로 전달받는 것입니다.
이러한 방식은 Unity에서도 찾아볼 수 있습니다.
if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, 10f))
{
Debug.Log(hit.point);
}
Physics.Raycast()는 광선을 발사하여 충돌 여부를 검사하는 메서드입니다.
충돌했는지는 bool 값으로 반환하고, 충돌 위치나 충돌한 객체 등의 정보는 out RaycastHit hit을 통해 전달합니다.
따라서 작업의 성공 여부와 실제 결과 데이터를 함께 전달해야 하는 상황에서 out을 유용하게 사용할 수 있습니다.
ref와 out 비교
지금까지 살펴본 두 키워드의 차이를 정리하면 다음과 같습니다.
| 구분 | ref | out |
| 전달 방식 | 참조에 의한 전달 | 참조에 의한 전달 |
| 호출 전 초기화 | 필수 | 불필요 |
| 기존 값 읽기 | 가능 | 할당 전 불가능 |
| 메서드 내부 값 할당 | 필수 아님 | 정상 종료 전 필수 |
| 주요 사용 목적 | 기존 값 읽기 및 수정 | 새로운 결과 전달 |
ref와 out은 모두 호출한 쪽의 변수를 직접 변경할 수 있습니다. 그렇기 때문에 상황에 따라서는 두 키워드로 비슷한 기능을 구현할 수도 있습니다.
하지만 기존 값을 사용해야 하는지, 새로운 결과를 전달해야 하는지에 따라 적합한 키워드가 달라집니다.
앞서 작성했던 Swap()은 기존 변수의 값을 읽어 서로 교환하는 기능이므로 ref가 적합합니다.
반대로 TryParse()는 변환하기 전의 결과값을 사용할 필요가 없고, 새로 변환한 결과를 전달하면 되므로 out이 적합합니다.
정리
ref와 out은 모두 매개변수를 참조로 전달하기 위해 사용하는 키워드입니다.
- ref : 기존 변수의 값을 읽고 수정해야 할 때 사용합니다.
- out : 메서드에서 새롭게 계산한 결과를 전달할 때 사용합니다.
다만 여기서 말하는 참조에 의한 전달은 class와 같은 참조 형식(Reference Type)과는 구분해야 합니다.
int와 같은 값 형식도 ref나 out을 사용하면 참조로 전달할 수 있습니다. 즉, 데이터 형식 자체가 참조 형식으로 바뀌는 것은 아닙니다.
결국 중요한 것은 메서드가 기존 값을 필요로 하는지, 아니면 새로운 결과를 전달하려는 것인지입니다.
이러한 차이를 이해하고 상황에 맞게 사용한다면 메서드가 어떤 데이터를 전달받고 수정하는지 보다 명확하게 표현할 수 있습니다.
'TIL' 카테고리의 다른 글
| [CS] 메서드와 매개변수 (0) | 2026.10.10 |
|---|---|
| [CS] Object와 박싱/언박싱 (0) | 2026.09.24 |
| [CS] 값 형식과 참조 형식 (0) | 2026.09.22 |
| [Unity 2D] 무기 시스템 (전략 패턴) (0) | 2026.04.23 |