CodeGym /행동 /C# SELF /파라미터 수정자 ( ref,

파라미터 수정자 ( ref, out, in)

C# SELF
레벨 12 , 레슨 4
사용 가능

1. 소개

C#에서는 기본적으로 모든 메서드 파라미터가 값으로 전달돼. 즉, 메서드에는 원본이 아니라 복사본이 들어가는 거지.

근데 만약 메서드 안에서 외부 변수를 바꿔야 한다면? 아니면 메서드에서 여러 값을 한 번에 반환하고 싶다면? 아니면 엄청 큰 구조체를 메서드에 넘겨야 하는데, 그걸 다 복사하기 싫고 그냥 참조만 넘겨서 메모리 아끼고 싶다면?

이 모든 게 바로 파라미터 수정자에 관한 거야. 이걸로 호출하는 코드랑 메서드 사이에서 데이터가 어떻게 전달되는지 직접 컨트롤할 수 있어.

수정자 종류

수정자 데이터 전달 호출 전에 초기화 필요? 내부에서 읽기 가능? 내부에서 쓰기 가능? 주요 사용 시나리오
ref 양방향 읽거나/바꾸려고 변수 전달할 때
out 밖으로만 아니 아니 (값 할당 전까지) 응 (무조건!) 메서드에서 여러 값 반환할 때
in 안으로만 아니 큰 구조체를 "읽기 전용"으로 넘길 때 메모리 아끼려고

걱정하지 마, 이제 하나씩 다 볼 거야. 생각보다 훨씬 쉬워.

2. ref 수정자: 참조로 넘기기 - 읽기도, 쓰기도 가능

상상해봐: 친구한테 커피 한 잔을 가져다주면서 "마셔도 되고, 데워도 되고, 뭐든 추가해도 돼"라고 말하는 거야. 친구는 그 커피로 뭐든 할 수 있고, 남은 게 있든 없든 다시 너한테 돌아와. ref가 딱 이런 느낌이야.


void DoSomething(ref int x)
{
    x = x + 10; // 입력 파라미터를 바꿔줌
}
        
ref 파라미터가 있는 메서드

ref로 파라미터를 넘기려면, 선언할 때도, 호출할 때도 이 수정자를 붙여야 해:

int myNumber = 5;
DoSomething(ref myNumber);
Console.WriteLine(myNumber); // 15 출력됨

무조건 변수여야 해. 참조로 식(expression)을 넘길 수 없어. 이런 코드는 안 돼:

DoSomething(ref 10);		// 여기엔 변수여야 해!
  • 호출 전: 변수는 반드시 초기화되어 있어야 해 (값이 할당되어 있어야 함).
  • 메서드 내부: 읽기도, 값 바꾸기도 가능.
  • 호출 후: 변경된 값이 그대로 남아있어.

예시: 변수 값 서로 바꾸기

가끔 두 변수의 값을 서로 바꿔야 할 때가 있어. 근데 여기서 중요한 점: C#에서 값 타입(예: int)은 기본적으로 값으로 전달돼. 즉, 메서드에 넘기면 값만 복사되고, 변수의 참조는 안 넘어가. 메서드가 호출한 쪽의 변수를 바꾸려면 ref 키워드를 써야 해.

ref 없이 예시 — 안 바뀜:

// 값 바꾸기 시도 — 안 바뀜
void Swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;
Swap(x, y); // 값 복사만 됨
Console.WriteLine($"{x}, {y}"); // → 10, 20 — 아무것도 안 바뀜!

Swap 함수 안의 abxy의 복사본이야. 복사본만 바뀌고, 원본은 그대로야.

ref로 동작하는 예시 — 변수 값이 바뀜


// ref로 변수 값 바꾸기
void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;
Swap(ref x, ref y); // 변수의 참조가 넘어감
Console.WriteLine($"{x}, {y}"); // → 20, 10 — 제대로 바뀜
        

ref는 변수의 참조를 넘겨주는 거야, 값이 아니라.
그래서 Swap 함수가 xy를 직접 바꿀 수 있어.

ref 언제 써?

  • 넘겨준 변수를 바꿔야 할 때 (예: 값 증가, 교체 등).
  • return으로 새 값을 반환할 필요 없이, 그냥 인자를 직접 바꾸고 싶을 때.
  • 여러 값을 "쏟아내기" 용도로는 쓰지 마 — 그럴 땐 다른 수정자가 더 좋아.

3. out 수정자: 밖으로만 넘기기

이번엔 이런 상황: 친구 집에 갔는데 빈 컵을 들고 "이거 좀 채워줘! 커피든 차든 뭐든!"이라고 하는 거야. 친구는 무조건 뭔가를 채워줘야 하고, 안 그러면 컴파일러가 난리남.

out은 메서드가 반환값(return) 말고 여러 값을 한 번에 넘겨줄 때 쓰는 거야. 이런 파라미터는 메서드 안에서 반드시 값이 할당되어야 해.


void GetCoordinates(out int x, out int y)
{
    x = 5;
    y = 10; // 이거 없으면 에러!
}
        
out 파라미터가 있는 메서드
  • 호출 전: 변수는 초기화 안 해도 돼. 호출할 때 out int x처럼 바로 써도 됨.
  • 메서드 내부: 반드시 값을 할당해야 해. 안 그러면 컴파일러가 파업함.
  • 호출 후: 변수에 새 값이 들어가 있음.

예시: 메서드에서 두 값 한 번에 반환하기

void ParseNameAndAge(string input, out string name, out int age)
{
    string[] parts = input.Split(',');	// 문자열을 배열로 쪼갬 - ',' 기준으로 나눔
    name = parts[0];
    age = int.Parse(parts[1]);
}

string userInput = "이반,25";
ParseNameAndAge(userInput, out string userName, out int userAge);
Console.WriteLine($"{userName} — {userAge} 살");

out 언제 써?

  • 메서드에서 여러 값을 반환해야 할 때 (예: 문자열을 여러 부분으로 나눌 때 등).
  • 반환 타입이 미리 정해지지 않았을 때 (예: 문자열을 숫자로 변환 시도 등).

.NET에서 자주 쓰는 예시: int.TryParse(string, out int) — 완전 대표적이야!

string input = "123";
if (int.TryParse(input, out int parsedNumber))
{
    Console.WriteLine($"변환됨: {parsedNumber}");
}
else
{
    Console.WriteLine("변환 에러!");
}

int.TryParse는 문자열을 숫자로 변환할 수 있으면 true를 반환하고, 실패하면 false를 반환해. 실제 숫자 값은 out으로 넘겨줘.

refout 쓸 때 흔한 실수와 웃긴 상황

  • ref 쓸 때 변수 초기화 안 하면 컴파일러가 화냄.
  • out에 값 할당 안 하면 컴파일러가 더 화냄.
  • 호출할 때 수정자 빼먹으면 안 됨: DoSomething(x) 대신 DoSomething(ref x)처럼 써야 해.
  • refout을 상수나 리터럴에 쓰면 절대 안 됨! 무조건 변수여야 해. 변수만 메모리 주소가 있어서 참조를 만들 수 있거든.

4. in 수정자: 읽기 전용, 참조로만

가끔 메서드에 엄청 크고 복잡한 객체를 넘겨야 할 때가 있어. 보통 이런 객체는 참조로 넘기는데, 그러면 함수가 실수로 그 객체를 바꿔버릴 수도 있지 — 우리가 참조를 넘겼으니까.

물론 객체를 복사해서 넘기면 원본은 안 바뀌겠지만, 객체가 크면 쓸데없이 데이터만 복사하게 돼. 차라리 객체를 함수에 넘기면서, "이건 읽기만 해!"라고 컴파일러한테 시키는 게 훨씬 효율적이야.

in구조체를 참조로 넘기면서 읽기 전용으로만 쓸 수 있게 해주는 새로운 수정자야. 약간 "진귀한 검을 유리관에 전시"하는 느낌: 볼 수는 있지만, 만질 수는 없어.


void PrintPoint(in Point pt)
{
    pt.X = 5; // 에러! 읽기만 가능.
    Console.WriteLine($"포인트: {pt.X}, {pt.Y}");  //이건 가능
}
        
in (읽기 전용) 파라미터가 있는 메서드
  • 호출 전: 변수는 반드시 초기화되어 있어야 해.
  • 메서드 내부: 읽기만 가능, 값 변경 불가.
  • 메모리 절약: 구조체가 복사되지 않고 참조로만 넘어감.

예를 들어, 큰 구조체 배열을 넘길 때 불필요한 복사를 피하려고 써. 근데 클래스(참조 타입)에는 in이 별 효과 없어.

예시: 큰 구조체를 참조로 넘겨서 복사 방지

struct BigData
{
    public int A, B, C, D, E;
}

void PrintBigData(in BigData data)
{
    data.A = 10; // 안 됨 - 읽기 전용!
    Console.WriteLine(data.A + data.B + data.C + data.D + data.E);
}

BigData myData = new BigData { A = 10, B = 20, C = 30, D = 40, E = 50 };
PrintBigData(in myData);

5. 자주 궁금해하는 질문들:

ref/out/in을 배열이나 참조 타입에도 쓸 수 있어?
당연하지! 근데 기억해: 배열 자체가 이미 참조 타입이야. ref로 배열을 넘기면 배열 참조 자체를 바꿀 수 있어. 보통은 구조체에 더 많이 써.

ref랑 out의 차이가 뭐야? 둘 다 값 받을 수 있잖아?
ref는 변수가 미리 초기화되어 있어야 하고, out은 안 그래도 돼. 대신 out은 메서드 안에서 무조건 한 번은 값 할당해야 해.

리터럴(예: 숫자 5)로 ref/out/in 쓰면?
컴파일러가 바로 에러 내. 무조건 변수만 가능!

ref/out/in 파라미터 몇 개까지 쓸 수 있어?
제한 없어! 근데 코드 읽기 힘들어지니까 2~3개 넘으면 튜플, 구조체, 클래스 반환을 고민해봐.

1
설문조사/퀴즈
클래스와 오브젝트, 레벨 12, 레슨 4
사용 불가능
클래스와 오브젝트
상속 소개
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION