티스토리 뷰

☞ ! 사전 지식!  :  이 부분에 대해서  잘 모르면 게시글 내용 읽어도 무슨 소리인지 헷갈리고 이해도 불가능..!

 

・메서드에 인수를 전달하는 방법에는 아래의 2가지가 있다.

- 값에 의한 자료 전달(call by value) : 변수의 값을 복사해서 전달하는 방식 → 이 게시글에서는 값 형식 전달이라고 하겠음

- 참조에 의한 자료 전달(call by reference) : 변수를 공유하는 방식 → 이 게시글에서는 참조 전달이라고 하겠음

 

・C#에서 다루는 형은 아래의 2가지가 있다.

- 값 형(value type) : int, long, decimal, char, byte 등이 값 형

- 참조형 (reference type): object, string, List <T> 제네릭 컬렉션, 배열

프로그램이 실행되는 효율과 메모리 공간을 사용하는 효율 때문에 값형과 참조형이 따로 존재하는 것

 

C#에서는 메서드의 인수 리스트에서 값 형식을 참조 전달하려면 in/out/ref의 세 가지 파라미터 한정자를 사용한다.

우선 in/out/ref 파라미터 한정자를 이용하면 파라미터의 전달을 효율적으로 할 수 있다.

이 세 한정자의 공통점, 차이점과 주의점을 정리해보자.


■ in/out/ref 파라미터 한정자의 공통점

모두 참조 전달을 한다.

: 호출하는 곳과 호출받는 곳 양쪽 모두 값을 설정하고 교환하기 위해! 

 

- 메소드의 인수 리스트에 기술하는 각각의 인수에 in/out/ref 파라미터 한정자 중 하나를 지정할 수 있다.

- 파라미터 한정자를 붙이는 순간 인수는 참조 전달로 이 메서드에 전달된다.

- 붙이지 않으면 인수는 값 형으로 전달

 

<예제>

// 참조 전달 : in/ref/out 붙였을 경우
void SampleMethodIn(in int n){ ……생략…… }
void SampleMethodRef(ref int n){ ……생략…… }
void SampleMethodOut(out int n){ ……생략…… }

// 값 형식 전달 : in/out/ref 안붙였을 경우
void SampleMethod(int n){ ……생략…… }

 

그리고 in/out/ref 파라미터 한정자에는

소스코드상에서는 뒤에 적는 내용에 따라서 차이가 있어도 컴파일 결과는 같다.(바이너리 레벨에서는 참조 전달 방법은 1개밖에 없음)

in/out/ref 파라미터 한정자의 차이는 소스코드를 분명하게 쓰는 것에 따라 다르다.

 

・즉시 실행되지 않는 메서드에서는 사용이 불가능함

참조 전달은 아래의 2종류 메서드에서는 사용이 불가능하다.

- Async 메서드 : 시그니처에 async 수식자를 붙인 메서드

- Iterator 메서드 : yield return / yield break를 포함하는 메서드 

 

※ Iterator 관련한 내용은 https://kr98gyeongim.tistory.com/99 참고하기

 

・오버로드 되는 경우, 안 되는 경우

- 컴파일러 입장에서는 in/out/ref 모두 동일한 참조 호출이기 때문에  in/out/ref 파라미터 한정자 차이로는 오버로드가 불가능하다.

- 하지만, in/out/ref의 유무로는 오버로드가 가능하다.

 

<예제 : in/out/ref 한정자의 차이로는 오버로드가 되지 않음.>

// 참조 전달
void SampleMethod(in int n){ ……생략…… }
// 아래 2 메서드는 오버로드X(위의 메소드와 동일 취급)
// void SampleMethod(ref int n){ ……생략…… }
// void SampleMethod(out int n){ ……생략…… }

// 값 형식 전달(in/out/ref 없음)
void SampleMethod(int n){ ……생략…… } // 참조 전달과 다른 메소드로 취급. 즉, 오버로드 대상

■ in/out/ ref 한정자의 차이점

세 한정자 모두 참조 전달이지만 아래의 용도에 따라 차이가 있다.

- in : 인수를 메서드에 입력용으로 사용한다.

- ref : 범용. 주로 값 형식의 인수를 변경시키기 위해 사용한다.

- out : 인수를 메서드로부터의 출력용으로 사용한다.

 

아래의 표를 보면 메서드를 호출하는 방법과 메서드 내부에서 그 인수로 할 수 있는 것에 대해 차이가 있다는 것을 확인할 수 있다.

한정자 용도 호출하기 전 변수 초기화 호출시의 한정자 키워드 지정 메서드 내에서 참조 대상 할당/ 변경 옵션 인수
in 입력 필수 선택(써도 되고 안써도 됨) 불가능 가능
ref 변경 필수 필수 가능 불가능
out 출력 불필요 필수 필수 불가능

위의 표에 대해서 설명을 해보자.

 

・in 파라미터 한정자

그 인수를 메서드에서 입력용으로 이용할 때만 사용한다.

그렇기 때문에 메서드를 호출하기 전에 변수를 초기화해서 값을 설정하는 것이 필수이다.

메서드 내에서는 인수를 참조하는(메서드를 호출하는 측의 변수)의 내용을 변경하거나 다른 오브젝트를 할당할 수 없다.

호출하는 곳에서 전달한 변수가 변경될 가능성값 형인 변수와 동일하므로 호출할 때 in 키워드의 지정은 선택 가능함.

입력 전용이기 때문에 옵션 인수(디폴트 인수)로도 할 수 있다.

 

값 형 전달 vs in 파라미터 한정자

그러면 값 형식 전달과 in 파라미터 한정자를 사용한 참조 전달의 차이는 뭘까?

그건 바로 큰 사이즈의 값 형식을 빠르게 전달할 수 있다는 것이다.

 

예제로 설명해보자면

1. 작은 사이즈의 값 형 데이터 int형인 경우에는

오브젝트의 크기와 참조 사이즈는 같으므로 복사용을 만드는 속도도 차이가 없다.

(32bit CPU를 타깃으로 빌드한 경우 int형의 데이터도 참조도 사이즈는 4byte이다.)

 

2. 하지만 큰  사이즈의 구조체인  경우에는

값 형을 전달할 때는 큰 오브젝트를 전체를 다 복사하고,

참조 전달할 때는 그 오브젝트에 비해서 작은 참조를 만들 뿐이라서 

값 형을 전달하는 것에 비해서 참조 전달이 빠를 수 있다는 것이다.

 

 

*복사란? : 기존 객체와 같은 값을 가지는 새로운 객체를 만든다는 것

*값 형에서의 복사 : 모든 멤버(값 형식, 참조 형식 모두)가 복사

*참조 전달에서의 복사 : 객체가 가진 멤버의 값을 새로운 객체로 복사, 만일 객체가 참조 타입의 멤버를 가지고 있다면 참조값만 복사

* 멤버는 값 형식일 수도 있고 참조 형식일 수도 있다.

 

ref 파라미터 한정자

그 인수를 메서드에서 입력용으로도 출력용으로도 이용할 때 사용한다.

in과 같이 입력용으로 이용하기 위해서는 메서드를 호출하기 전에 변수를 초기화해서 값을 설정해 주는 것이 필수이다.

하지만 in과 다르게 출력용으로 이용이 가능하므로 메서드 내에서는 인수를 참조하는(메서드를 호출하는 측의 변수)의 내용을 변경하거나 다른 오브젝트를 할당하여 출력용으로도 이용할 수 있다.

메소드를 호출하는 곳에서 값을 전달한 변수가 고쳐질 가능성을 생각해야 하므로 ref의 키워드는 필수로 지정해야 한다.

 

out 파라미터 한정자

그 인수를 메서드에서 출력용으로 이용할 때만 사용한다.

그렇기 때문에 메서드를 호출하기 전에 변수를 초기화를 할 필요는 없다.

C# 7.0에서는 메서드를 호출하는 인수 리스트의 괄호 안에서 변수의 선언도 가능하다.

메서드 내에서는 인수를 참조하는(메서드를 호출하는 측의 변수)에 객체를 할당하는 것이 필수이다.

또한 호출할 때 out 키워드도 필수로 지정해야 한다.

 

여기까지가 표에 대한 설명이고 글도 길고 무슨 말을 하는지 머리가 아프다.. 실제로 예제를 보면서 이해해보자,,


--------------------------------------------------------------여기서부터는 모두 예제--------------------------------------------------------------

 

■ 값 형 데이터를 넘기는 예제

사전 설정!

아래와 같은 구조체를 가지고 예제를 다룰 것이다.

public struct SampleStruct
{
  public double X { get; set; }
  public double Y { get; set; }
}

위의 구조체는 16바이트이다. 사이즈가 참조보다 크므로 참조 전달로 하면 값 형식 전달보다 속도가 빠를 것이다.

 

이 구조체를 인수로 받은 메서드에서 그 속성을 변경하거나 인스턴스를 다시 할당했을 때

실제로 어떤 동작을 하는지 확인해보자.

 

・값 형의 전달의 경우

그 인수를 전달받은 메서드에서 속성 값을 변경하거나 새로운 인스턴스를 할당할 수 있다.

↓ 값 형식으로 전달받은 메서드의 예제

static void SampleMethod(SampleStruct s)
{
  s.X = 3.0;
  s = new SampleStruct();
  s.X = 4.0;
}

하지만 이 변경이나 할당은 호출을 한 곳의 변수에는 영향을 주지 않는다.

왜냐?

값 형식 전달은 변수의 내용(이 경우는 SampleStruct 객체)의 복사본을 메서드에 전달하기 때문이다.

 

↓ 값 형을 전달하는 코드의 예제

: 출력 결과를 보면 변수 s의 내용은 메서드 호출 전과 다르지 않다는 것을 확인할 수 있다.

// Main 메서드 내부
SampleStruct s = new SampleStruct { X = 1.0, Y = 2.0, };
SampleMethod(s);
WriteLine($"값 형식 전달:X={s.X}, Y={s.Y}");
// 출력 결과:값 형식 전달:X=1, Y=2

 

・in 파라미터 한정자를 사용해서 참조 전달을 하는 경우

인수를 전달받은 메서드 측에서는 속성의 값을 변경하거나 새로운 인스턴스를 할당할 수 없다.

 

↓ 값 형을 in 참조 전달로 받는 메서드의 예제

: in 파라미터 한정자가 붙어 있는 값 형의 전달 인수에 대해서는 

그 멤버의 값을 변경하거나 새로운 인스턴스를 할당하려고 했을 때 컴파일 에러가 된다.

static void SampleMethodIn(in SampleStruct s)
{
  // 아래 전부 다 컴파일 에러 발생
  //s.X = 3.0;
  //s = new SampleStruct();
  //s.X = 4.0;
}

 

in 파라미터 한정자를 사용해 값 형을 참조 전달하는 경우

 

↓ 값 형을 in 참조 전달로 전달하는 코드의 예제

: 출력 결과를 보면 변수 s의 내용은 메서드 호출 전과 다르지 않다.

※ 만약 SampleMethodIn 메서드에 값 형 전달과 참조 전달의 2개의 오버로드가 있었을 경우에는

호출할 때의 in 키워드 유무로 호출되는 메서드가 다르다.

// Main 메서드 내부
SampleStruct s = new SampleStruct { X = 1.0, Y = 2.0, };
SampleMethodIn(s);
SampleMethodIn(in s); // in은 써도 안써도 됨.
WriteLine($"값 형을 전달(in):X={s.X}, Y={s.Y}");
// 출력 결과:값 형을 전달(in):X=1, Y=2

 

・ref 파라미터 한정자를 사용해 참조 전달을 하는 경우

그 인수를 전달 받은 메서드 측에서는 속성 값을 변경하거나 새로운 인스턴스를 할당할 수 있다.

 

↓ 값 형식을 ref 참조 전달로 전달 받는 메서드의 예제

: ref 파라미터 한정자가 붙어있는 값 형식의 인수에 대해서는 해당 멤버의 값을 변경하거나 새로운 인스턴스를 할당할 수 있다.

static void SampleMethodRef(ref SampleStruct s)
{
  s.X = 3.0;
  s = new SampleStruct();
  s.X = 4.0;
}

 

・ref 파라미터 한정자를 사용해 값 형을 참조 전달했을 경우

↓ 값 형식을 ref 참조 전달로 전달 하는 코드의 예제

: 출력결과를 보면 속성 X가 바뀌었을 뿐만 아니라 속성 Y가 0으로 바뀌었다.

이는 메서드 내에서 새로운 인스턴스가 할당되었기 때문이다.

// Main 메서드 내부
SampleStruct s = new SampleStruct { X = 1.0, Y = 2.0, };
SampleMethodRef(ref s); // ref를 쓰지 않으면 컴파일 에러 발생
WriteLine($"값 형식을 참조 전달(ref):X={s.X}, Y={s.Y}");
// 출력결과:값 형식을 참조 전달(ref):X=4, Y=0

 

・out 파라미터 한정자를 사용해 값 형을 참조 전달했을 경우

그 인수를 받은 메서드의 측에서는 새로운 인스턴스를 할당하지 않으면 안 된다.

 

↓ 값 형을 out 참조 전달로 받는 메서드의 예제

: out 파라미터 한정자가 붙어 있는 인수에 대해서는 새로운 인스턴스를 할당해야 한다.

안 하면 컴파일 에러가 난다.

static void SampleMethodOut(out SampleStruct s)
{
  //s.X = 3.0; // 이 행은 컴파일 오류(미할당 파라미터 사용)
  s = new SampleStruct(); // 새 오브젝트 할당
  s.X = 4.0;
}

 

・out 파라미터 한정자를 사용해 값 형을 참조 전달하는 경우

호출하기 전에 변수를 초기화하지 않아도 된다.

C# 7.0에서는 호출할 때 인수 리스트 내에서 변수 선언이 가능하다.

// Main 메서드 내부
SampleMethodOut(out SampleStruct s); // out을 적지않으면 컴파일 에러
WriteLine($"값 형식을 참조 전달(out):X={s.X}, Y={s.Y}");
// 출력 결과:값 형식을 참조 전달(out):X=4, Y=0

 

■ 배열(참조 전달)을 전달하는 예제

클래스 등의 참조형을 참조 전달하는 의미는 별로 없지만 실제로 어떻게 돌아가는지 확인해보자.

사용하지 않으면 안 될 이유가 없으면 참조형의 참조 전달은 사용하지 않는 게 좋다. 배열이나 List<T>제네릭 컬렉션 등도 참 조형이므로 여기에서는 정수 배열로 예로 들겠음!

 

・값에 의한 전달일 경우

인수를 받은 메서드의 측에서는 배열의 요소를 변경하거나 새로운 인스턴스를 할당할 수 있다.

 

↓ 참조형을 값에 의한 전달로 받는 메서드의 예제

static void SampleMethod(int[] a)
{
  a[0] = 2;
  a = new int[5];
  a[0] = 3;
}

 

배열 요소 변경은 메서드를 호출하는 곳의 변수에 영향을 미치지만 새로운 배열 할당은 호출원 변수에는 영향을 주지 않는다.

 

↓참조형을 값에 의한 전달로 전달하는 코드의 예제: 결과를 확인해 보면 배열 요소 a [0]는 변경되었으나, 메서드 내에서 행해진 새로운 배열의 할당은 반영되어 있지 않다.(a[1] 이후의 값이 초기화되지 않고 1인 상태 그대로 유지하고 있음)

// Main 메서드 내부
int[] a = { 1, 1, 1 };
SampleMethod(a);
WriteLine($"배열을 값에 의한 전달로 전달:{string.Join(", ", a)}");
// 출력 결과:배열을 값에 의한 전달로 전달:2, 1, 1

값에 의한 전달은 변수 내용(이 경우에는 배열의 실체에서 참조)의 복사를 메서드에 전달하기 때문에 참조되는 곳은 (여기서는 배열의 실체) 호출을 하는 곳과 메서드에서 동일하며 배열 요소의 변경은 호출을 하는 곳에 영향을 미친다. 메서드 내에서 새로운 배열을 할당하는 것은 참조 복사본에 대해서이기 때문에 호출하는 곳의 참조에는 영향을 주지 않는다.

 

다음으로 in 파라미터 한정자를 사용해 참조 전달을 하는 경우이다.

in 파라미터 한정자를 사용해 참조 전달 인수를 받은 메서드 측에서는 참조처에 새로운 인스턴스를 할당할 수 없다.

다만, in 파라미터 한정자로 자주 헷갈리는데 호출되는 곳의 참조는 변경할 수 있다.

즉, 이 경우에는 배열의 요소는 변경할 수 있다는 것이다.

 


<정리>

- 인수의 참조 전달은 주로 값 형식(value type)으로 사용된다.

in 파라미터 한정자는 메서드의 입력으로 큰 구조체를 넘기는 경우, 빠르게 처리하기 위해 사용한다.

ref 파라미터 한정자는 주로 복수의 값 형식을 메서드 측에서 변경된 값을 받기 위해 사용한다.

out 파라미터 한정자는 메서드로부터 여러 결과를 받기 위해서 사용한다.


 

<작성 이유>

회사에서 백엔드 로직을 구현하면서 ref와 out을 사용하는 일이 있었는데
나중에 또 사용할 일이 있을 수 있으니까 까먹지 않게 ref와 out의 차이가 무엇인지 알아본 내용에 대해서 메모.
플러스로 in이라는 파라미터 한정자도 있어서 이것도 같이 작성하려고 한다.
참고로 in 파라미터 한정자는 C#7.2의 신기능이므로 7.2이후부터 사용가능함! (회사 C# 버전이 3.0이라 쓸일이 없다,,ㅎ)
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함