메소드

메소드는 객체지향 프로그래밍 언어에서 사용하는 용어로 C, C++ 언어에서는 함수라고 불린다.

메소드는 일련의 코드를 하나의 이름 아래 묶은 것이다. 이렇게 묶은 코드는 메소드의 이름을 불러주는 것만으로 실행할 수 있어 코드를 간추리고, 읽기 쉽고 이해하기 쉽게 만든다.

메소드는 다음과 같은 형식으로 선언한다.

class 클래스_이름
{
	한정자 반환_형식 메소드_이름(매개변수_목록)
	{
		// 실행 코드1
		// 실행 코드2
		// ...
		
		return 메소드_결과;
	}
}

C#은 객체지향 프로그래밍 언어이기 때문에 클래스 안에 메소드가 존재한다. 메소드를 객체의 일을 처리하는 방법 또는 방식으로 이해할 수 있다.

매개변수, 반환 형식

클래스 안에 선언되는 메소드는 **매개변수(Parameter)**와 **반환 형식(Return Type)**을 갖는다.

매개변수는 제품을 만들기 위해 기계(메소드)에 집어넣는 재료라고 할 수 있으며, 반환 형식은 만들어지는 제품의 규격이라고 할 수 있다.

만약 결과를 반환하지 않는 경우에는 void 반환 형식을 사용한다.

다음 그림은 메소드의 호출 과정을 설명한다.

image.png

매개변수는 메소드가 호출자에게서 전달받은 값을 받는 변수를 말하고, 인수는 호출자가 매개변수에 넘기는 값을 뜻한다.

메소드의 호출

다음 그림은 메소드 호출 시 일어나는 프로그램 흐름의 변화를 설명한다.

image.png

CLR은 왼쪽 코드를 쭉 실행하다가 Calculator.Plus 메소드를 호출한다. 이때 프로그림의 흐름이 Plus 메소드로 이동한 후 메소드가 담고 있는 코드를 차례대로 수행한다.

메소드 블록의 끝에 도달하거나 Plus 메소드에서처럼 return 문을 만나면 메소드가 종료되고 원래의 프로그램 흐름으로 돌아와 계속 실행한다.

using System;

namespace Method
{
    class Calculator
    {
        public static int Plus(int a, int b)
        {
            Console.WriteLine("Input : {0}, {1}", a, b);
            return a + b;
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            int x = Calculator.Plus(3, 4);
            Console.WriteLine($"x : {x}");

            int y = Calculator.Plus(5, 6);
            Console.WriteLine($"y : {y}");
            
            int z = Calculator.Plus(7, 8);
            Console.WriteLine($"z : {z}");
        }
    }
}
Input : 3, 4
x : 7
Input : 5, 6
y : 11
Input : 7, 8
z : 15

return

return 문은 점프문의 한 종류로 프로그램의 흐름을 호출자에게로 돌려놓는다. return 문은 언제든지 메소드 중간에 호출되어 메소드를 종결시키고 프로그램의 흐름을 호출자에게 돌려줄 수 있다.

메소드가 반환할 것이 아무것도 없는 void 반환 형식인 경우에도 return 문을 사용할 수 있다.

using System;

namespace Return
{
    class MainApp
    {
        static int Add(int num1, int num2)
        {
            return num1 + num2;
        }

        static void PrintProfile(string name, string phone)
        {
            if ("" == name)
            {
                Console.WriteLine("이름을 입력해주세요.");
                return;
            }

            Console.WriteLine($"Name : {name}, Phone : {phone}");
        }

        static void Main(string[] args)
        {
            Console.WriteLine($"4 + 5 = {Add(4, 5)}");

            PrintProfile("", "123-4567");
            PrintProfile("홍길동", "456-1230");
        }
    }
}
4 + 5 = 9
이름을 입력해주세요.
Name : 홍길동, Phone : 456-1230

매개변수

값에 의한 전달

매개변수도 근본적으로 '변수'이며, 메소드 외부로부터 메소드 내부로 데이터를 전달받는 매개체 역할을 한다. 즉, 인수를 매개 변수로 넘길때 데이터가 복사될 뿐이다.

아래 코드는 Swap 메소드 안에서 두 매개변수를 교환했지만, 원본 Main 메소드 안의 원본 변수에는 아무런 영향을 주지 못하는 것을 보여준다.

using System;

namespace RecursiveCall
{
    class MainApp
    {
        static void Swap(int a, int b)
        {
            int temp = b;
            b = a; 
            a = temp;
        }

        static void Main(string[] args)
        {
            int x = 3;
            int y = 4;

            Console.WriteLine($"x : {x}, y : {y}");

            Swap(x, y);

            Console.WriteLine($"x : {x}, y : {y}");
        }
    }
}
x : 3, y : 4
x : 3, y : 4

참조에 의한 전달

참조에 의한 전달을 이용하면 매개변수가 메소드에 넘겨진 원본 변수를 직접 참조한다. 따라서 메소드 안에서 매개변수를 수정하면 이 매개변수가 참조하고 있는 원본 변수에 수정이 이루어진다.

메소드의 선언에서 매개변수 앞에 ref 키워드를 붙여 참조에 의한 매개변수 전달을 선언할 수 있고, 메소드를 호출할 때 ref 키워드를 인자 앞에 붙여주면 된다.

using System;

namespace RecursiveCall
{
    class MainApp
    {
        static void Swap(ref int a, ref int b)
        {
            int temp = b;
            b = a; 
            a = temp;
        }

        static void Main(string[] args)
        {
            int x = 3;
            int y = 4;

            Console.WriteLine($"x : {x}, y : {y}");

            Swap(ref x, ref y);

            Console.WriteLine($"x : {x}, y : {y}");
        }
    }
}
x : 3, y : 4
x : 4, y : 3

참조에 의한 반환

참조 반환값을 이용하면 메소드의 호출자로 하여금 반환받은 결과를 참조로 다룰 수 있도록 한다.

ref 키워드를 이용해 메소드를 선언하고, return 문이 반환하는 변수 앞에 ref 키워드를 명시해 참조 반환값을 이용할 수 있다.

class SomeClass
{
	int SomeValue = 10;
	
	public ref int SomeMethod()
	{
		// ...
		return ref SomeValue;
	}
}

호줄자가 특별한 키워드를 사용하지 않는 한 참조로 반환하는 메소드는 값으로 반환하는 평범한 메소드처럼 동작한다.

SomeClass obj = new SomeClass();
int result = obj.SomeMethod(); // 값으로 반환하는 메소드로 작동

호출자가 반환 결과를 참조로 넘겨 받고 싶다면 ref 키워드를 사용한 참조 지역 변수를 사용해야 한다. 참조 지역 변수를 변경하면 원본도 변화한다.

SomeClass obj = new SomeClass();
ref int result = ref obj.SomeMethod(); // 참조 지역 변수
using System;

namespace RefReturn
{
    class Product
    {
        public ref int GetPrice()
        {
            return ref price;
        }

        public void PrintPrice()
        {
            Console.WriteLine($"Price : {price}");
        }

        private int price = 100;
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Product carrot = new Product();
            ref int ref_local_price = ref carrot.GetPrice();
            int normal_local_price = carrot.GetPrice();

            carrot.PrintPrice();
            Console.WriteLine($"Ref Local Price : {ref_local_price}");
            Console.WriteLine($"Normal Local Price : {normal_local_price}");

            ref_local_price = 200;

            carrot.PrintPrice();
            Console.WriteLine($"Ref Local Price : {ref_local_price}");
            Console.WriteLine($"Normal Local Price : {normal_local_price}");
        }
    }
}
Price : 100
Ref Local Price : 100
Normal Local Price : 100
Price : 200
Ref Local Price : 200
Normal Local Price : 100

출력 전용 매개변수

메소드의 결과는 대부분 하나면 충분하지만, 두 개 이상의 결과를 요구하는 경우도 존재한다.

이럴때 out 키워드를 이용한 출력 전용 매개변수를 이용할 수 있다.

void Divide(int a, int b, out int quotient, out int remainder)
{
	quotient = a / b;
	remainder = a % b;
}

ref 키워드를 이용해도 결과를 저장할 수 있지만 out 키워드에는 ref 에 없는 안전장치가 있다.

ref 키워드를 이용해 매개변수를 넘기는 경우 메소드가 해당 매개변수에 결과를 저장하지 않아도 컴파일러는 아무 경고도 하지 않는다.

그러나 out 키워드를 이용할경우 매개변수를 넘길 때는 메소드가 해당 매개변수에 결과를 저장하지 않으면 컴파일러가 에러 메시지를 출력한다.

또한 컴파일러가 호출당하는 메소드에서 그 지역 변수를 할당할 것을 보장하기 때문에 메소드를 호출하는 쪽에서 초기화하지 않은 지역 변수를 메소드의 out 매개변수로 넘길 수 있다.

마지막으로 출력 전용 매개변수는 메소드를 호출하기 전에 미리 선언할 필요가 없다. 호출할 때 매개변수 목록안에서 즉석으로 선언하면 된다.

using System;

namespace Out
{
    class MainApp
    {
        static void Divide(int a, int b, out int quotient, out int remainder)
        {
            quotient = a / b;
            remainder = a % b;  
        }

        static void Main(string[] args)
        {
            int a = 20;
            int b = 3;
            // int c;
            // int d;

            Divide(a, b, out int c, out int d);
            Console.WriteLine($"a : {a}, b : {b}, a/b : {c}, a%b : {d}");
        }
    }
}
a : 20, b : 3, a/b : 6, a%b : 2

가변 개수의 인수

가변 개수의 인수란, 그 개수가 유연하게 변할 수 있는 인수를 말한다. 가변 개수의 인수는 형식은 같으나 인수의 개수만 유연하게 달라질 수 있는 메소드에 사용할 경우에 적합하다.

가변 개수의 인수는 params 키워드와 배열을 이용해서 선언한다.

using System;

namespace UsingParams
{
    class MainApp
    {
        static int Sum(params int[] args)
        {
            Console.Write("Summing... ");

            int sum = 0;

            for (int i = 0; i < args.Length; ++i)
            {
                if (i > 0)
                    Console.Write(", ");
            
                Console.Write(args[i]);
                sum += args[i];
            }

            Console.WriteLine();
            return sum;
        }
        
        static void Main(string[] args)
        {
            int sum = Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
            Console.WriteLine($"Sum : {sum}");
        }
    }
}
Summing... 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Sum : 55

명명된 인수

메소드를 호출할 때 매개변수 목록 중 어느 매개변수에 데이터를 할당하지 지정하는 것을 매개변수의 순서를 지정한다고 한다.

대부분 순서에 근거해서 매개변수에 인수를 할당하는 스타일을 사용하지만, C#은 명명된 인수라는 또 다른 스타일도 지원한다.

명명된 인수 (Named Argument)를 이용하면 메소드를 호출할 때 인수의 이름에 근거해서 데이터를 할당할 수 있다.

메소드를 호출할 때 인수의 이름 뒤에 콜론 : 을 붙인 뒤 그 뒤에 할당할 데이터를 넣어주면 명명된 인수를 사용할 수 있다.

using System;

namespace NamedParameter
{
    class MainApp
    {
        static void PrintProfile(string name, string phone)
        {
            Console.WriteLine($"Name : {name}, Phone : {phone}");
        }

        
        static void Main(string[] args)
        {
            PrintProfile(name: "박찬호", phone: "010-1234-1234");
            PrintProfile(phone: "010-9876-9876", name: "박지성");
            PrintProfile("박세리", "010-2222-2222");
            PrintProfile("이상혁", phone: "010-1111-1111");
        }
    }
}
Name : 박찬호, Phone : 010-1234-1234
Name : 박지성, Phone : 010-9876-9876
Name : 박세리, Phone : 010-2222-2222
Name : 이상혁, Phone : 010-1111-1111

선택적 인수

메소드의 매개변수는 기본값을 가질 수 있다. 기본값을 가진 매개변수는 메소드를 호출할 때 해당 인수를 생략할 수 있다.

기본값을 가짐 매개변수는 필요에 따라 인수를 할당하거나 할당하지 않을 수 있기 때문에 이를 선택적 인수(Optional Argument) 라 한다.

선택적 인수는 항상 필수 인수 뒤에 와야한다.

using System;

namespace OptionalParameter
{
    class MainApp
    {
        static void PrintProfile(string name, string phone = "None")
        {
            Console.WriteLine($"Name : {name}, Phone : {phone}");
        }

        static void Main(string[] args)
        {
            PrintProfile("철수");
            PrintProfile("영희", "010-1234-1234");
            PrintProfile(name : "길동");
            PrintProfile(name : "대박", phone : "010-1111-1111");
        }
    }
}
Name : 철수, Phone : None
Name : 영희, Phone : 010-1234-1234
Name : 길동, Phone : None
Name : 대박, Phone : 010-1111-1111

선택적 인수는 메소드 오버로딩과 함께 사용할 경우 혼란을 야기할 수 있으므로 함께 사용하지 않는것이 좋다.

로컬 함수