1. 메소드(Method)

이번 강좌에서는 메소드가 무엇인지, 메소드가 어떠한 기능을 하는지, 또 어떻게 쓰이는지 알아보도록 하겠습니다. C#에서의 메소드(Method)는, C언어와 C++의 함수(Function)와 비슷한 기능을 합니다. 메소드를 간단히 나타내자면, 이어지는 코드들을 묶어놓은 코드 블록입니다. 예를 들어서, 아래는 제곱 후 결과물을 출력하는 기능을 가진 메소드입니다.

...
    static void square(int a) {
        Console.WriteLine("{0}*{1}={2}", a, a, a*a);
    }
...

위 코드는, square라는 녀석에게 값을 넘겨주고, 임시로 a라는 변수에 값을 기억시킵니다. 그리고 이 a 변수를 가지고 제곱하여 출력하는 코드입니다. 대충 메소드를 어떻게 정의하는지 보이시나요? 아래는 C#에서 메소드를 정의하는 방법입니다.

[접근 지정자] 반환형식 메소드명(매개변수 목록) {
    // 실행될 코드
}

여기서 접근 지정자에는, private, public, protected 등이 있습니다. 말 그대로 접근 가능한 범위를 지정할 수 있으며, 접근 지정자에 대한 설명은 나중에 더 자세히 다룰 예정입니다. 반환 형식은 메소드의 결과에 따라 다릅니다. 반환되는 데이터의 형식이 int형이라면, int를 써야하며 double형이라면 double을 반환형식에다 적어주어야 합니다. 만약에 반환되는 값이 없을 경우에는 void를 적으시면 됩니다. 그리고 매개변수 목록은 메소드 호출 시 값을 넘길 수 있도록 하는 '매개변수'가 1개 이상 등장합니다.


아래의 예제는 반환형식의 이해를 돕기위한 예제입니다.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("{0}", Division(40, 10));
        }

        static int Division(int a, int b)
        {
            return a / b;
        }
    }
}
결과:
4
계속하려면 아무 키나 누르십시오 . . .
우선은 예제의 9행을 봅시다. 이상한 게 있다면 인자 부분에 변수나 어느 값이 아닌 Division라는 메소드가 등장했습니다. 15행의 Division라는 메소드를 살펴봅시다. 이 메소드는 두 정수를 매개변수로 받습니다. 우리는 40과 10이란 값을 각각 넘겼으므로, 40이란 값은 매개변수 a에 저장되고, 10이란 값은 매개변수 b에 저장됩니다. 그러곤 메소드가 이 a, b를 이용하여 결과를 만들어 호출부로 반환(return)합니다.

return 부분을 보자면, a가 40, b가 10이므로 결과는 4가 됩니다. 그러곤 return를 만나 4라는 값을 호출된 부분으로 반환합니다. 즉, 이 메소드를 불러냈던 곳으로 결과를 다시 보낸다는 말과 같습니다. 결과가 4이므로 Division(40, 10)은 4라는 값을 가지게 됩니다. 그래서 결과처럼 4라는 결과가 출력될 수 있었던 거죠. 그리고 한가지 주의할점이 있는데, return 키워드는 맨 마지막에서만 등장하는 것이 아니라 코드의 중간에서도 등장할 수도 있습니다. 또한, return 키워드를 만나게 되면 메소드에서 빠져나온다는 사실을 알고 계셔야 합니다. 

2. Call by value vs Call by reference
Call by value와 Call by reference가 무엇인지 설명하기전에, 예제를 한번 짚고 넘어갑시다. 아래 예제에서는 두 매개변수의 값을 교환하는 Swap란 함수가 등장합니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 40;
            int b = 10;

            Console.WriteLine("Swap before: a={0}, b={1}", a, b);

            Swap(a, b);

            Console.WriteLine("Swap after: a={0}, b={1}", a, b);
        }

        static void Swap(int a, int b)
        {
            int temp = b;
            b = a;
            a = temp;
        }
    }
}
결과:
Swap before: a=40, b=10
Swap after: a=40, b=10
계속하려면 아무 키나 누르십시오 . . .
먼저, 코드를 보자면 19행에서 Swap란 함수가 등장합니다. Swap 함수의 내용을 보자면 값을 임시로 담아둘 temp란 변수를 선언하고 temp란 변수의 값을 b의 값으로 초기화시킵니다. 그러고는 b에 a의 값을 담습니다. 그리고 a에는 temp(원래 b의 값)을 담습니다. 즉, Swap 함수는 a와 b의 값을 서로 바꿔버리는 기능을 수행합니다. 여기서 이렇게 매개변수를 변수의 값으로 가져온 경우를 Call by value(복사에 의한 함수 호출)이라고 부릅니다. 

다시 9행으로 돌아가, 정수형 변수 a와 b가 선언되었으며 그와 동시에 40과 10으로 각각 초기화되었습니다. 그러곤 Swap 함수가 실행되기 전의 a, b의 값을 출력한 뒤에 Swap 함수가 실행되고 다시 한번 a, b의 값을 출력합니다. 그러나 Swap 함수가 실행됨에도 불구하고 a와 b의 값은 바뀌지 않습니다. 왜 값이 바뀌지 않았던 걸까요?

우리가 메소드로부터 변수를 넘겨줄 때부터, 매개변수 a와 b이 변수 a와 b를 가르키는 게 아니고, 그저 변수 a와 b의 값을, 매개변수 a와 b로 복사하는 것 뿐입니다. 한마디로 말하자면, 매개변수 a와 b, 변수 a와 b는 서로 별개이며, 다른 메모리 공간을 사용합니다. 그저 매개변수 a와 b의 값이 서로 바뀌었을 뿐, 변수 a와 b의 값은 그대로인 셈이죠. 그렇다면 변수 a와 b의 값을 바꾸도록 하려면 어떻게 해야할까요? 바로 Call by reference(참조에 의한 호출)로 넘긴다면 가능합니다. Call by reference는 Call by value와는 달리, 변수의 주소 값을 매개변수로 보냅니다. 직접 원래의 변수를 참조하는 방법입니다.

한번 위의 예제를 Call by reference로 넘겨보도록 하겠습니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 40;
            int b = 10;

            Console.WriteLine("Swap before: a={0}, b={1}", a, b);

            Swap(ref a, ref b);

            Console.WriteLine("Swap after: a={0}, b={1}", a, b);
        }

        static void Swap(ref int a, ref int b)
        {
            int temp = b;
            b = a;
            a = temp;
        }
    }
}
결과:
Swap before: a=40, b=10
Swap after: a=10, b=40
계속하려면 아무 키나 누르십시오 . . .
아까의 예제와 비교해보면 ref라는 새로운 녀석이 등장합니다. ref 키워드는 매개변수를 참조형식으로 사용할 때 사용되는 키워드입니다. 메소드의 선언, 호출 부분에 ref 키워드를 달아주면 그게 곧 Call by reference로 넘겨주는 방법입니다. 결과를 살펴보면 a와 b의 값이 서로 바뀐 것을 확인하실 수 있습니다. 즉, 원본의 데이터의 값이 바뀐 것을 알 수 있습니다. 

3. 메소드 오버로딩(Method Overloading)
우리가 알고 있는 그대로 C언어에서는 오버로딩이 불가능했습니다. 메소드명을 중복하여 다르게 구현하는 것이 불가능했죠. 그런데 C#에선 메소드 오버로딩을 지원합니다. 같은 메소드명에 인자만 다르게 하여 매개변수의 데이터 형식, 수에 따라 그에 맞는 코드를 실행할 수 있게 됐습니다. 아래는 오버로딩의 예입니다.
..
    static int Add(int a, int b) {
        Console.WriteLine("두 int형 끼리의 덧셈");
        return a + b;
}

    static int Add(double a, double b) {
        Console.WriteLine("두 double형 끼리의 덧셈");
        return a + b;
    }

    static int Add(int a, int b, int c) {
        Console.WriteLine("세 int형 끼리의 덧셈");
        return a + b + c;
    }
..
위 예제를 보시면 같은 메소드명인데도 불구하고 여러 번 등장합니다. 이렇게 똑같은 메소드명인데도 불구하고 매개변수에 따라 호출되는 메소드가 다릅니다. 아래는 오버로딩의 예입니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("{0}", Add(50, 10));
            Console.WriteLine("{0}", Add(544.2, 63.2));
            Console.WriteLine("{0}", Add(4, 7, 9));
        }

        static int Add(int a, int b)
        {
            Console.WriteLine("두 int형 끼리의 덧셈");
            return a + b;
        }

        static double Add(double a, double b)
        {
            Console.WriteLine("두 double형 끼리의 덧셈");
            return a + b;
        }

        static int Add(int a, int b, int c)
        {
            Console.WriteLine("세 int형 끼리의 덧셈");
            return a + b + c;
        }
    }
}
결과:
두 int형 끼리의 덧셈
60
두 double형 끼리의 덧셈
607.4
세 int형 끼리의 덧셈
20
계속하려면 아무 키나 누르십시오 . . .
코드의 9행을 보시면, Add 메소드로 세 번이나 다른 형태의 값들을 전달하였음에도 불구하고 결과를 보시면 제대로 출력됨을 확인할 수 있습니다. 14행에 있는 Add 메소드는 두 정수가 넘어올 때 호출되고, 20행에 있는 Add 메소드는 두 실수가 넘어올 때 호출되며, 26행에 있는 Add 메소드는 세 정수가 넘어올 때 호출됩니다. 이처럼, 매개변수의 형식과 수에 따라 호출되는 메소드가 다름을 알 수 있습니다. 유용하죠?

4-1. ref
우리가 방금 넘어왔던 Call by reference 예제에서 ref 키워드를 한번 만났었죠? ref 키워드의 역할은 대충 알고 계시겠지만, 다시 한번 짚고 넘어가도록 하겠습니다. ref 키워드를 사용하면 변수의 값을 그대로 전달하는 게 아닌, 변수의 메모리 주소를 전달한다고 기억합시다. 아래는 ref 키워드가 사용된 예제입니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 40;

            Add(ref a, 100);
            Console.WriteLine("a={0}", a);

            Add(ref a, 200);
            Console.WriteLine("a={0}", a);

            Add(ref a, 300);
            Console.WriteLine("a={0}", a);
        }

        static void Add(ref int a, int b)
        {
            a += b; // a = a + b;
        }
    }
}
결과:
a=140
a=340
a=640
계속하려면 아무 키나 누르십시오 . . .
코드를 보시면, ref 키워드가 메소드를 호출할때도 쓰였고 매개변수에서도 사용됬습니다. Add 메소드의 매개변수에 변수 a의 주소값을 넘겨주는 셈이죠. 실제 a의 주소를 안 Add 메소드는 a에다 매개변수 b의 값을 a에다 더합니다. 결과를 출력해봤더니, 정상적으로 덧셈이 진행됨을 알 수 있습니다. ref 매개변수를 사용하려면 메소드를 호출할때나, 메소드를 정의할때도 ref 키워드를 명시적으로 사용해주어야 합니다.

4-2. out
out 키워드는 ref 키워드와 비슷하게 인수를 참조로 전달할때 사용됩니다. 그러나 차이점이 존재합니다. out 키워드를 사용하면 변수를 전달하기전 초기화해야하는 ref 키워드와는 달리 초기화 하지 않고도 전달이 가능합니다. 아래는 out 키워드의 사용 예제입니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a;

            Add(out a);
            Console.WriteLine("a={0}", a);
        }

        static void Add(out int a)
        {
            a = 100;
        }
    }
}
결과:
a=100
계속하려면 아무 키나 누르십시오 . . .
12행을 보시면 정수형 변수 a가 등장하나 초기화는 되지 않은것을 확인할 수 있습니다. 14행에서는 Add 메소드가 등장하고 매개변수로 변수 a의 주소값을 넘깁니다. 18행에서의 Add 메소드에서, 이 a의 주소값을 가지고 a에 접근하게 됩니다. a에 100이란 값을 대입합니다. 그러면 a가 가지고 있는 값은 100이 되죠. 메소드를 빠져나와 a의 결과를 출력해보면 성공적으로 a의 값이 100이 됨을 알 수 있습니다.

4-3. params
우리가 만약, 길이에 제한받지 않고 수를 넘겨주어 그 수의 총 합을 구하고 싶을때는 어떻게 하면 될까요? 바로 params 키워드를 사용하면 쉽게 구할 수 있습니다. params 키워드의 기능은 메소드에 여러개의 값을 전달할 수 있도록 도와줍니다. 아래는 params 키워드가 사용된 예제입니다.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("sum={0}", total(20, 10, 40, 4, 7, 6, 44, 55, 2));
            Console.WriteLine("sum={0}", total(30, 4, 5));
        }

        static int total(params int[] list)
        {
            int sum = 0;

            for (int i = 0; i < list.Length; i++)
                sum += list[i];

            return sum;
        }
    }
}
12~13행의 코드를 보시면 길이에 제한 없이, 값을 total 메소드로 넘겨줍니다. 그러면, 16행의 total 메소드를 살펴보도록 합시다. 매개변수 부분에 params 키워드가 사용됬고 int형 뒤에 []가 붙었습니다. 여기서 params 키워드 말고도 배열이란 개념이 사용되었으나, 우리는 아직 배열을 배우지 않았으므로 간단히 이해하고 넘어가도록 하겠습니다. 배열(Array)은 변수들이 줄지어 서있는 것과 같다고 생각하시면 됩니다. 변수들의 모임이죠. 즉, int[]는 정수형 배열을 의미합니다. 우리가 total 메소드로 20, 10, 40, 4, 7, 6, 44, 55, 2를 넘김으로써 list에는,
list = {20, 10, 40, 4, 7, 6, 44, 55, 2}
위와 같이 저장되게 됩니다. 그러고 이 list를 가지고 하나하나 접근하여 sum이란 변수에 더하고 반복문을 빠져나와 sum의 값을 반환(return)합니다. for문을 잠깐 살펴보자면, list.Length는 배열 list의 길이를 구해주는 역할을 합니다. 그리고 for문 내를 보면 list[i]처럼 배열에 들어있는 각 요소에 접근할 경우에는 첨자값(index, 인덱스)값으로 접근합니다. 아직은 배열을 배우지 않아 이해가 안되실지도 모르겠지만, 여기서 중요한건 params 키워드를 사용하여 매개변수의 길이를 유연하게 만들수 있음을 아셔야 합니다. 

오늘의 강좌는 여기서 마치도록 하겠습니다. 수고하셨습니다.

다음 강좌에서는 배열(Array)에 대해 설명합니다.