C#에는 값을 참조형으로 사용하기 위한 두가지의 키워드를 제공해준다.

바로, ref와 out이다.

더보기

참조 전달이란, 값을 그대로 전달하는 것이 아니라 값이 저장된 메모리 영역이 어디인지를 전달하는 것이다.

 

값을 그대로 전달하게 되면, 함수 내부에서 그 값을 사용하기 위해 복사가 발생하게 된다. 하지만, 메모리 영역의 위만 전달하게 된다면 함수 내부에선 값을 복사하지 않고도 위치를 참조해 변수에 직접 접근할 수 있게 된다.

 

이해가 안된다면, call by value와 call by reference에 대해 검색해보자.

두 키워드는 무슨 차이가 있을까? 사실 근본적으로는 거의 비슷하다. 역할도 기능도 방식도 그렇다.

하지만, 차이점은 몇 가지 존재한다.

 

1. ref 키워드가 사용된 변수는 가리키는 대상이 초기화가 되어있어야 하지만, out은 초기화가 되어 있지 않아도 된다.

void Func()
{
    int A = 3;
    int B;
    
    //오류X
    Add_5(ref A);
    
    //오류 발생
    Add_5(ref B); 
}

void Add_5(ref int input)
{
    input += 5;
}

Add_5 함수에 값을 참조전달하기 위해 ref키워드를 사용하게 되면 B의 경우엔 오류가 발생한다.

이유는 대상이 초기화가 되어 있지 않기 때문이다.

하지만, Add_5의 파라미터를 out int 로 변경하여 사용한다면 또 다른 에러가 발생할 것이다. 이유는 아래 조건 때문이다.

 

2. out 키워드가 사용된 변수는 함수 내부에서 사용되기 전에 초기화가 반드시 이루어져야 한다.

void Func()
{
    int A = 3;
    int B;

    //오류X
    Set_5(out A);

    //오류X
    Set_5(out B);
}

void Add_5(out int input)
{
    //오류 발생
    input += 5;
}

void Set_5(out int input)
{
    input = 5;
}

위의 함수를 보면, Add_5함수는 값에 5를 더해주는 형식으로 사용하고 있다.

그런데, 사용할 변수의 값이 초기화가 안되어있기 때문에 값을 더하는 연산은 의도하지 않은 결과를 일으킬 수 있다.

이러한 위험성을 제거하기 위해 out키워드를 사용해 전달된 값은 반드시 내부에서 초기화를 해주어야 한다.

 

Set_5 함수의 경우 내부에서 값을 초기화해주고 있기 때문에, 에러가 발생하지 않는다.

이처럼 out키워드를 사용하여 참조 전달을 할 때엔, 대상을 초기화하지 않은 상태로 전달해도 되지만 그 내부에서는 우선적으로 초기화가 이루어져야만 한다.

 

3. out 키워드는 함수의 파라미터에만 사용이 가능하지만, ref는 그 외에도 사용할 수 있다.

void Func()
{
    //오류X
    int A = 3;
    ref int B = ref A;

    //오류 발생
    int C = 5;
    out int D = out C; 
}

이런걸 쓸 일이야 거의 없겠지만, 위처럼 ref는 같은 스코프 내에서 변수를 선언해서 사용할 수도 있다.

또한, C# 버전에 따라 멤버 변수에도 사용할 수가 있다.

 

반면, out키워드는 위와 같은 사용이 불가능하다.

 

ref와 out을 구분하는 이유

위의 차이를 생각해보면 사실 out키워드를 사용할 필요는 딱히 없어보인다.

ref 키워드로 out 키워드를 완전히 대체할 수 있기 때문이다.

 

그럼에도 불구하고 out 키워드가 존재하는 이유는 뭘까?

바로 그 의미를 확실하게 하기 위함이다.

 

참조 전달(call by reference)를 사용하는 이유는 크게 2가지가 있다.

 

1. 외부 변수의 값을 사용하기 위해 (읽기)

2. 외부 변수의 값을 변경하기 위해 (쓰기)

 

1번의 경우 간단하다. 외부에 있는 어떠한 변수의 값을 함수 내부에서 사용하고 싶은데 값형으로 전달하게 되면 복사가 발생하기 때문에 참조형을 통해 불필요한 복사를 막기 위해 참조 전달을 사용하는 것이다.

 

예를 들어 원소가 10만개인 배열을 사용할 때, 값형으로 전달하게 되면 10만번의 복사가 발생하지만, 참조전달을 하게 되면 이러한 복사가 발생하지 않기 떄문이다.

 

즉 성능 측면에서 큰 이득이 있기 때문이다.

 

2번의 경우는 어떨까? 이 역시도 1번과 동일하게 복사를 줄이고 성능을 향상시키기 위함이 크다.

 

만약 함수 내부에서 연산 결과 값을 배열에 저장하여 10만개의 원소를 보유하게 되었다고 했을 때, 이 배열을 값형으로 반환하게 되면 저장하는 과정에서 복사가 발생하게 될 것이다. 반면, 빈 배열을 참조형으로 전달한 뒤에 함수 내부에서 원소를 삽입하게 되면 불필요한 복사가 발생하지 않게 될 것이다.

 

결국 참조 전달은 성능의 향상을 위해 사용하는 것이 가장 큰 목적인 것이다.

그런데 위에서 말했듯이, ref 키워드만 있어도 두 이유를 모두 만족시킬 수 있다.

 

하지만, 코드를 좀 쳐본 사람이라면 알겠지만 프로그래밍에 있어서 가독성은 아주아주 중요한 역할을 한다. 이 변수가 어떻게 사용될 지 함수의 선언만 보고도 어느정도 유추할 수 있게 설계하는 것은 아주 중요한 일이다. out 키워드는 이를 확실히 하기 위해 사용된다.

 

함수의 파라미터가 out으로 선언되어 있다면, 우리는 그 키워드만 보고서 "아, 변수를 넣어주면 함수 내부에서 그 안에 값을 넣어주겠구나" 라는 예측이 가능하다는 것이다.

 

ref키워드만 모두 사용하게 된다면, 이를 구분할 수가 없을 것이다.

 

즉, 변수가 어떻게 사용되는지 그 의미를 확실하게 알려주기 위해 사용되는 키워드인 것이다.

하지만, out 키워드를 사용했는데 내부에서 값을 저장해주지 않는다면? 

 

외부에서 그 함수를 호출해 변수를 전달한 사람은 변수에 올바른 값이 저장되어 있을 것이라고 생각했겠지만, 당연히 알 수 없는 값이 들어있을 것이고 이는 의도치 않은 오류를 야기시킬 것이다.

 

즉, 가독성을 높이려다가 안정성을 엄청나게 해치는 꼴이 되어버릴 수 있다는 것이다. 이러한 실수를 방지하기 위해, out키워드를 사용해 전달한 변수는 함수 내부에서 반드시 초기화가 이루어지도록 강제되고 있다. 언어 차원에서 프로그래머의 실수를 막아주는 것이다.

 

즉, out은 함수 내부에서 값을 저장해주는 상황에 사용하는 키워드이며 ref는 내부에서 값을 사용할 때 사용카는 키워드라고 생각하면 좋다. (물론 ref를 사용해도 내부에서 값을 저장할 수 있다. 하지만, out과 그 역할을 구분하여 사용하는 편이 좋다.)

'C# > C#' 카테고리의 다른 글

C# - LINQ (Language Integrated Query)  (0) 2024.09.02
C# - const, readonly  (0) 2024.08.10
C# - is, as 연산자  (0) 2024.08.07
C# - 클래스, 접근 제한 지정자, 프로퍼티  (0) 2024.07.27
C# - C#의 자료형  (0) 2024.07.24

+ Recent posts