[C#] 5.1 캡슐화 대상(5.1.3 메서드와 매개 변수 전달 방식)

5.1.3 메서드와 매개 변수 전달 방식

메서드는 수행해야 할 작업에 대한 코드가 있는 블록입니다. 메서드는 이름과 수행에 필요한 입력 매개 변수와 수행한 결과 형식을 선언하고 블록 내에서 수행할 코드를 정의해야 합니다. 메서드의 입력 매개 변수는 여러 개가 올 수 있으면 콤마를 통해 구분하게 됩니다. 그리고 반환 형식은 하나만 정의할 수 있으며 필요가 없으면 void 로 선언합니다.

int DoAny(int a, int b)
{
    return a + b;
}

입력 매개 변수는 전달하는 방법에는 값으로 전달하는 방법과 참조로 전달하는 방법이 있습니다. 선언문에 단순히 형식과 변수 이름만 명시하면 값을 복사하여 전달되며 out이나 ref를 명시하면 참조로 전달됩니다. 값을 복사하여 전달하면 호출한 곳의 변수와 호출받은 곳의 변수는 독립적이며 참조로 전달되면 두 곳의 변수가 같은 개체를 참조합니다.

▶ 값 방식과 참조 방식으로 매개 변수 전달

static void Main(string[] args)
{
    int a = 2, b = 2, c = 2;
    Foo(a, ref b, out c);
    Console.WriteLine("Main 메서드 a:{0} b:{1} c:{2}", a, b, c);
}
private static void Foo(int a, ref int b, out int c)
{
    a = 3;
    b = 3;
    c = 3;
    Console.WriteLine("Foo 메서드 a:{0} b:{1} c:{2}", a, b, c);
}

▶ 실행 결과

Foo 메서드 a:3 b:3 c:3
Main 메서드 a:2 b:3 c:3

참조 방식으로 전달하는 방식에는 out과 ref이 있습니다.

ref 방식으로 전달하는 매개 변수는 메서드를 수행하는 데 필요한 인자이면서 메서드 내에서 변경되면 호출한 곳에서도 알 필요가 있을 때 사용됩니다. 이러한 이유로 ref 매개 변수는 호출하는 곳에서는 반드시 의미 있는 값을 가진 변수를 전달해야 합니다.

의미없는 값을 가진 ref 인자 전달 시 오류 화면
[그림 13] 의미없는 값을 가진 ref 인자 전달 시 오류 화면

 out 방식으로 전달되는 매개 변수는 반환 형식이 하나라는 한계를 극복하기 위해 사용되며 호출하는 곳에서 전달하는 변수의 값을 초기화하지 않아도 됩니다. 대신 피 호출 메서드 내에서는 반드시 out 형식의 매개 변수의 값을 변경해야 합니다.

out 매개 변수를 변경하지 않았을 때 오류 화면
[그림 14] out 매개 변수를 변경하지 않았을 때 오류 화면

 다음의 예는 두 수의 차가 예측한 값과 같은지를 판별하고 실제 차이 값을 확인하는 메서드를 통해 ref 매개 변수를 간략하게 살펴봅시다. GapCheck 메서드는 두 수의 차가 ref 매개 변수로 전달된 값과 같으면 true를 반환하고 그렇지 않으면 ref 매개 변수에 차이를 대입하고 false를 반환합니다.

▶ 전달 방식이 ref인 매개 변수 사용 예

static void Main(string[] args)
{
    int a = 10, b = 3;
    int gap = 8;
    Console.WriteLine("예상 {0},{1} 차이: {2}",a,b,gap);
    if (GapCheck(a, b, ref gap))
    {
        Console.WriteLine("예측이 맞았군요.");
    }
    else
    {
        Console.WriteLine("예상과 다르군요.");
    }
    Console.WriteLine("실제 {0},{1} 차이: {2}",a,b,gap);
}
private static bool GapCheck(int a, int b, ref int gap) //gap:예상 값
{
    if ((a - b) == gap)
    {
        return true;
    }
    gap = a - b; //실제 차이를 대입
    return false;
}

▶ 실행 결과

예상 10,3 차이:8
예상과 다르군요.
실제 10,3 차이:7

다음은 두 수의 합과 차를 계산해 주는 메서드에 대한 예입니다. AddAndGap 메서드에서는 두 수의 합을 반환하고 두 수의 차는 out 방식으로 전달한 변수를 통해 알 수 있습니다.

▶ out 매개 변수 사용 예

class Program
{
    static void Main(string[] args)
    {
        int a = 2, b = 3;
        int sum, gap;
        sum = AddAndGap(a, b, out gap);
        Console.WriteLine("sum:{0} gap:{1}", sum, gap);
    }

    //두 수의 합을 반환하고 두 수의 차이를 gap(out 방식으로 전달)에 대입하는 메서드
    private static int AddAndGap(int a, int b, out int gap)
    {
        gap = a - b;
        return a + b;
    }
}

▶ 실행 결과

sum:5 gap:-1

값과 참조에 관한 얘기는 형식에서도 언급한 바가 있는데 여기에서 얘기한 것은 매개 변수 전달 방식에 대한 것입니다. 앞의 예는 모두 값 형식의 인자를 값 방식과 참조 방식으로 전달하는 예를 들었는데 여기에서는 참조 형식의 인자를 값 방식과 참조 방식으로 전달하는 예를 들기로 하겠습니다.

이들에 대한 설명에 앞서 값 형식과 참조 형식의 차이에 대한 간략한 설명과 예를 들어볼게요.

3장 형식 개요에서 값 형식의 변수는 변수 선언을 통해 메모리가 할당되어 변수마다 독립적으로 할당된 메모리를 사용할 수 있다고 하였습니다. 이에 다른 변수를 선언하고 대입을 하면 각 변수를 위해 할당된 메모리는 독립적이므로 단순히 값이 복사됩니다.

이에 반해 참조 형식의 변수는 변수 선언이 아닌 개체 생성을 통해 메모리가 할당되고 변수는 개체를 참조합니다. 이에 개체를 참조하는 변수를 다른 변수에 대입하면 두 개의 변수는 같은 개체를 참조하게 됩니다. 따라서 하나의 변수로 변경하면 다른 변수도 같은 개체를 참조하므로 개체의 정보를 확인해 보면 같이 변하는 것을 알 수 있습니다.

▶ 값 형식과 참조 형식의 차이

using System;

namespace Ex_Method
{
    struct ExStruct //구조체 - 값 형식
    {
        public int Val
        {
            get;
            set;
        }
    }

    class ExClass //클래스 - 참조 형식
    {
        public int Val
        {
            get;
            set;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //값 형식에 관한 테스트 코드
            ExStruct es1 = new ExStruct();
            es1.Val = 4;
            ExStruct es2 = es1;
            es2.Val = 5;
            Console.WriteLine("es1.Val:{0}, es2.Val:{1}", es1.Val, es2.Val);

           //참조 형식에 관한 테스트 코드
            ExClass ec1 = new ExClass();
            ec1.Val = 4;
            ExClass ec2 = ec1;
            ec2.Val = 5;
            Console.WriteLine("ec1.Val:{0}, ec2.Val:{1}", ec1.Val, ec2.Val);
        }
    }
}

▶ 실행 결과

es1.Val:4, es2.Val:5
ec1.Val:5 ec2.Val:5

이제 참조 형식을 값 방식으로 전달하는 경우에 대해 살펴봅시다.

값 형식을 값 방식으로 전달하면 호출한 곳과 피 호출 메서드의 변수는 독립적입니다. 단지 호출한 곳의 값을 복사하여 전달받는 것이며 피 호출 메서드에서 변수의 값을 변경하였을 때 호출한 곳의 변수는 아무런 영향을 받지 않습니다.

하지만 참조 형식을 값 방식으로 전달하면 호출한 곳과 피 호출 메서드의 변수는 같은 개체를 참조하게 됩니다. 따라서 피 호출 메서드에서 개체의 정보를 변경하였을 때 호출한 곳의 변수도 같은 개체를 참조하므로 변경된 정보를 알 수 있습니다.

▶ 값 형식과 참조 형식을 값 방식으로 매개 변수 전달했을 때 비교

class Program
{
    static void Main(string[] args)
    {
        ExStruct es = new ExStruct();
        ExampleValueType(es); // 값 형식을 값 방식으로 전달
        Console.WriteLine("es.Val:{0}",es.Val);
        ExClass ec = new ExClass();
        ExampleReferenceType(ec); //참조 형식을 값 방식으로 전달
        Console.WriteLine("ec.Val:{0}", ec.Val);
    }
    private static void ExampleReferenceType(ExClass ec)
    {
        ec.Val = 5;
    }
    private static void ExampleValueType(ExStruct es)
    {
        es.Val = 5;
    }
}

▶ 실행 결과

es.Val:0
ec.Val:5

이번에는 참조 형식을 참조 방식으로 전달하는 경우에 대해 살펴보기로 합시다.

참조 형식을 값 방식으로 전달하더라도 피 호출 메소드에서 개체의 정보가 변경되었을 때 호출한 곳에서도 같은 개체를 참조하므로 해당 개체의 정보 변화를 알 수 있습니다. 그렇다면 언제 참조 형식을 참조 방식으로 전달해야 할까요?

만약, 참조 형식을 값 방식으로 전달받은 메서드에서 새로운 개체를 생성하여 대입하면 어떻게 될까요? 이 경우에 호출하는 곳에서는 호출 시에 전달한 개체를 참조하고 있기 때문에 피 호출 메서드에서 새로운 개체를 생성한 것을 알 수가 없게 됩니다. 호출한 곳에서도 새로 생성한 개체를 알아야 한다면 참조 방식으로 전달해야 합니다.

▶ 참조 형식을 값 방식과 ref 방식으로 전달했을 때 비교

static void Main(string[] args)
{
    ExClass ec1 = new ExClass();
    ExampleValueParam(ec1); //참조 형식을 값 방식으로 전달
    Console.WriteLine("ec1.Val:{0}", ec1.Val);
    ExClass ec2 = new ExClass();
    ExampleReferenceParam(ref ec2); //참조 형식을 ref 방식으로 전달
    Console.WriteLine("ec2.Val:{0}", ec2.Val);
}
private static void ExampleReferenceParam(ref ExClass ec)
{
    ec = new ExClass();
    ec.Val = 5;
}
private static void ExampleValueParam(ExClass ec)
{
    ec = new ExClass();
    ec.Val = 5;
}

▶ 실행 결과

ec1.val:0
ec2.Val:5