본문 바로가기
프로그래밍/C#

C# Call by value와 Call by reference에 대해서

by bantomak 2024. 1. 12.

먼저 읽고 오면 좋은 글

 

C# Value Type vs Reference Type

값 타입(Value Type) 스택 메모리에 저장 데이터를 직접 가지고 있음 기본 타입들 대부분이 값 타입 (Booleans, Characters, DateTime, TimeSpan, 구조체) 값 타입 변수를 복사하면 완전히 새로운 변수가 생기고

jettstream.tistory.com

 

Call by value

우리는 함수를 호출할 때 함수에 인자로 넣어서 함수를 실행시킨다. 이때, 인자로 넘겨지는 객체는 그 자체가 전달되는 것이 아니라 복사되어서 넘어간다.

 

public void main()
{
   int x = 3;
   Foo(x);
   
   Console.WriteLine(x); // 3
}

public void Foo(int y)
{
    y = 5;
}

 

Foo 함수에 인자로 넘겨진 x는 그 자체로 Foo 함수에 돌입하는 것이 아니라 복사된 상태로 Foo 함수에 돌입한다.

 

x != y 이 둘은 동일하지 않다. 별도의 메모리에 존재하는 별도의 객체이다.

그래서 함수를 통해서 y를 아무리 변경해 봤자 x는 변경되지 않는다.

 

이것이 Call by value를 의미한다. 우리는 함수에 값만 복사해서 사용한 것이다.

 

Call by value로 값 타입을 넘길 경우

x와 y의 주소가 다르다.
y를 아무리 털어도 x한테는 영향이 없다.

 

그러면 여기서 넘겨지는 인자가 값 타입인지 참조 타입인지에 따라서 어떤 차이가 있을 것인가?

 

Call by value로 참조 타입을 넘길 경우

참조 타입인 string을 이용해서 작성하면 물론 동일한 결과가 출력될 것이다. 하지만 내부는 다르다.

 

public void main()
{
   string x = "3";
   Foo(x);
   
   Console.WriteLine(x); // "3"
}

public void Foo(string y)
{
    y = "5";
}

 

call by value로 복사된 참조 타입에 다른 값이 할당된 모습

 

  • Call by value + 값 타입
  • Call by value + 참조 타입

결과 값은 동일하지만 내부 동작이 다르다. 

 

Call by value 예제 코드

복사된 값이 전달되기 때문에 호출자는 변경된 값을 확인할 수 없다.

List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
List<string> stringList = new List<string>() { "1", "2", "3", "4", "5" };

list.ForEach(x => x = 3);
stringList.ForEach(x => x = "3");

Console.WriteLine(string.Join(",", list));       // 1,2,3,4,5
Console.WriteLine(string.Join(",", stringList)); // 1,2,3,4,5

 

 

call by value로 값 타입을 전달하면 기존 객체에 전혀 영향을 못주지만 call by reference로 넘겨진 객체를 통해서 변경이 가능하다.

 

class Pos
{
    public int x { get; set; }
    public int y { get; set; }

    public Pos(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

static void main()
{
    List<Pos> posList = new List<Pos>() { new Pos(1, 2), new Pos(2, 4) };

    posList.ForEach(p => p.y = 5);

    foreach(var p in posList)
    {
        Console.WriteLine($"{p.x},{p.y}");  // 1, 5
                                            // 2, 5
    }
}

 

참조 타입을 call by value로 넘겼지만 해당 객체의 데이터를 변경하였다.

 

이런 경우에는 호출자도 변경된 데이터에 대해서 확인이 가능하다.

 

Call by reference

이번에는 Call by reference에 대해서 알아보자

 

public void main()
{
   int x = 3;
   Foo(x);
   
   Console.WriteLine(ref x); // 5
}

public void Foo(ref int y)
{
    y = 5;
}

 

x와 y의 주소가 같다.

 

Foo 함수에 넘겨진 인자 y는 x 그 자체이다.

 

x == y 이 둘은 동일하다. 동일한 객체이다.

그래서 함수를 통해서 y를 변경하면 x도 함께 변경된다.

 

Call by reference로 값 타입을 넘길 경우

 

x와 y는 동일한 객체이다.

 

Call by reference로 참조 타입을 넘길 경우

이번에도 참조 타입을 인자로 넘기는 경우에 대해서 알아보자

public void main()
{
   string x = "3";
   Foo(ref x);
   
   Console.WriteLine(x); // "5"
}

public void Foo(ref string y)
{
    y = "5";
}

 

x와 y는 동일한 객체이다. y가 변경되면 x도 함께 변경된다.

 

  • Call by reference + 값 타입
  • Call by reference + 참조 타입

 

정리하자면

  • Call by value + 값 타입
  • Call by value + 참조 타입
  • Call by reference + 값 타입
  • Call by reference + 참조 타입

이렇게 4가지 종류에 대해서 이해하고 있다면 C#을 이해하는데 더욱 도움이 될 거라고 생각한다.

 

이렇게까지 고민한 이유

함수에 파라미터로 인자를 넘기면서 이해 안 되는 부분들이 있었다. Call by value로 인자를 복사해서 넘겼는데 해당 객체의 데이터를 변경하면 실제로 적용이 되는 부분이었다. 해당 부분에 대해서 고민하다가 실제로 메모리 구조를 그려보니 이해가 되었다. 인자로 넘겨지면 동일한 참조를 가지도록 복사된 것이고 이 참조를 통해 데이터의 변경을 변경하면 원본에 대한 변경이기 때문이다.

댓글