Search
Duplicate

C#/ 매개변수와 키워드 - ref, in, out, this

call by value, call by reference

C#에서 메서드에 파라미터를 넘기면 기본적으로 변수를 가리키는 stack이 복사되어 전달된다.
값형 변수는 데이터가 stack에 존재하기 때문에 같은 값을 갖는 다른 변수가 넘어간다. 이 때문에 전달 받은 매개변수를 사용하는 메서드 내에서 변수의 값을 변경하여도 원본 값은 변경되지 않는다.
static void UpdateNum(int num) { num = 10; Console.WriteLine("Num: {0}", num); // 값은 10 } static void Main() { int num = 5; UpdateNum(num: 5); Console.WriteLine("Num: {0}", num); // 값은 5 }
C#
복사
참조형 변수는 데이터가 heap에 존재하며, stack에는 그 heap의 주소를 가리키는 값이 들어 있기 때문에 stack이 복사되면 동일한 주소를 갖는 다른 변수가 생성되고, 이를 전달 받은 메서드 내에서 참조형 변수를 수정하면 해당 주소가 가리키는 heap의 데이터를 수정하게 되어 원본의 값도 변경되게 된다.
static void UpdateNumArr(int[] numArr) { numArr[0] = 2; Console.WriteLine("NumArr First : {0}", numArr[0]); // 값은 2 } static void Main() { int[] numArr = { 1, 5, 10 }; UpdateNumArr(numArr: numArr); Console.WriteLine("Num Arr First: {0}", numArr[0]); // 값은 2 }
C#
복사
이게 바로 call by value, call by reference의 개념. 경우에 따라 pass by value, pass by reference라고 부르기도 한다. 둘은 같은 개념.

ref 키워드

C#에서 참조형 변수라고 처리되는 class의 인스턴스도 C++에서는 stack에 생성할 수 있기 때문에 C++에서는 매개변수를 넘길 때 변수의 참조를 넘기지 않으면 stack을 복사해서 넘기기 때문에 복사 비용이라는 것이 발생한다. —이런 맥락으로 C#에서 시작한 프로그래머는 C++에서 복사 비용을 고려하며 코드를 작성해야 하는 번거로움을 겪어야 한다. C#에서는 주소가 넘어가기 때문에 복사 비용이 없음.
개인적으로 그것 때문에 처음에는 C#에서 왜 매개변수 앞에 ref라는 키워드를 사용하는 것에 의문이 있었는데, 아래 참고자료 (1번) 을 보고 그 차이를 이해했다. ref 키워드를 쓰지 않아도 메서드 내부에서 참조 변수의 값을 변경할 수는 있지만, ref를 쓰지 않으면 새로운 할당을 할 수 없다는 것이 차이다.
static void ChangeNumArr(int[] numArr) { numArr = new int[] { -2, -4, -6 }; Console.WriteLine("NumArr First : {0}", numArr[0]); // 값은 -2 } static void ChangeNumArrByRef(ref int[] numArr) { numArr = new int[] { -2, -4, -6 }; Console.WriteLine("NumArr First : {0}", numArr[0]); // 값은 -2 } static void Main() { int[] numArr = { 1, 5, 10 }; ChangeNumArr(numArr: numArr); Console.WriteLine("Num Arr First: {0}", numArr[0]); // 값은 1 ChangeNumArrByRef(numArr: ref numArr); Console.WriteLine("Num Arr First: {0}", numArr[0]); // 값은 -2 }
C#
복사
참조 변수를 받은 메서드 내부에서 참조 변수에 새로운 할당을 하면 그 메서드 내부에서는 영향이 있지만, 원본에는 영향이 없다. 반면 ref 키워드를 이용해서 전달 받은 변수를 내부에서 새롭게 할당하면 그것은 원본에도 영향을 미친다.
바로 이러한 이유로 ref 키워드를 사용한다.

out 키워드

ref 키워드는 메서드 내부에서 참조형 변수를 재할당할 수 있음을 의미하지만, out 키워드는 메서드 내부에서 재할당을 강제하는 키워드를 의미한다. 이러한 이유로 ref 키워드의 변수는 메서드에 전달하기 전에 반드시 할당이 되어야 하지만 —null 값이라도— out 키워드의 변수는 메서드에 전달 하기 전에 할당이 되어 있지 않아도 문제가 없다.
static void ChangeNumArrByRef(ref int[] numArr) { numArr = new int[] { -2, -4, -6 }; Console.WriteLine("NumArr First : {0}", numArr[0]); // 값은 -2 } static void ChangeNumArrByOut(out int[] numArr) { numArr = new int[] { -2, -4, -6 }; Console.WriteLine("NumArr First : {0}", numArr[0]); // 값은 -2 } static void Main() { int[] numArr; ChangeNumArrByRef(numArr: ref numArr); // 에러. 변수를 할당해야 함. ChangeNumArrByOut(numArr: out numArr); // ok }
C#
복사
위 코드만 봐서는 out 키워드를 왜 쓰나 싶은데, 개인적으로는 메서드에서 반환해야 하는 값이 2개 이상이거나 TryGetValue 와 같이 코드를 깔끔하게 사용하기 위해 사용한다.
static bool TryGetNum(IEnumerable<int> values, int findNum, out int num) { foreach (int value in values) { if (value == findNum) { num = value; return true; } } num = -1; return false; } static void Main() { int[] numArr = new int[] { 1, 3, 5, 7, 9 }; if (TryGetNum(values: numArr, findNum: 2, num: out int num)) { // 값을 찾았을 때 실행 코드 } else { // 값을 못 찾았을 때 실행 코드 } }
C#
복사
사실 반환값 여러개는 tuple로도 사용할 수 있으며, C# 7.0 이후에는 Tuple Deconstruction이라는 표현법을 통해 아예 여러 개의 변수를 return 받을 수도 있다.

in 키워드

C++이나 JAVA에는 매개변수를 넘길 때 메서드 내부에서 매개변수를 변경할 수 없도록 하는 const라는 키워드를 사용할 수 있다. C#에서는 그런게 없어서 아쉬웠는데, 7.2 버전 이후에 같은 의미로 in 이라는 키워드를 사용할 수 있게 되었다. —그런데 왜 이름을 다른 언어들처럼 const가 아니라 in 으로 썼는지는 모르겠다.
static void UpdateNum(in int num) { num = 10; // error } static void Main() { int num = 5; UpdateNum(num: num); }
C#
복사

this 키워드

개인적으로 이 글을 정리하게 된 계기가 된 키워드. 확장 메서드를 쓰지 않다보니 매개변수 앞에 this 라는 키워드를 보고 놀라서 찾아보고 겨우 이해하게 되었다.
매개 변수 앞에 쓰이는 this 키워드는 위의 ref, out, in 과 같이 메서드 내부에서 사용하는 것에 대한 것이 아니라 확장 메서드를 사용할 때 사용하는 것으로 이 키워드를 사용하면 C# 컴파일러가 해당 메서드를 확장 메서드로서 이해할 수 있다.
namespace ExtensionMethods { public static class MyExtensions { public static int WordCount(this String str) { return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; } } }
C#
복사
확장 메서드는 위와 같이 선언한다. 메서드에 this 키워드를 선언하고 그 뒤에 그 확장 메서드를 붙일 Class Type과 해당 class의 instance을 적어주면 C# 컴파일러는 해당 Class에 확장 메서드를 사용할 수 있게 해준다. 그러면 아래 코드와 같이 사용할 수 있다.
string s = "Hello Extension Methods"; int i = s.WordCount(); // i = 3
C#
복사
프로그래머가 자체적으로 선언한 클래스에 대해서는 해당 클래스에 직접 메서드를 구현하면 되기 때문에 사용할 일이 없을테지만, 위와 같이 .NET에서 제공하는 클래스나 외부 라이브러리 등 클래스 내부에 손대기 어려운 경우에 위와 같이 확장 메서드를 사용할 수 있다.
그런데 개인적으로는 남의 class에 저렇게 확장 메서드를 붙이는 것보다는 별도의 static 클래스를 만들고 그것을 활용하는 것이 더 낫다고 생각 함. 구조적으로도 깔끔하고, 협업하기에도 낫다. 코드를 처음 보는 다른 사람이 string에 WordCount라는 메서드가 있을거라고 생각하기 쉽지 않으니까
public static class Util { public static int WordCount(string str) { return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; } } static void Main() { string s = "Hello Extension Methods"; int i = Util.WordCount(s); // i = 3 }
C#
복사

참고 자료