반응형

앞서 메서드와 매개변수에 대해 알아보았습니다.
 
메서드는 특정 기능을 수행하는 코드의 묶음이며, 매개변수를 통해 외부에서 전달받은 값을 사용할 수 있습니다.
그런데 메서드를 사용하다 보면 같은 기능을 수행하더라도 전달하는 인수의 자료형이나 개수가 달라지는 경우가 있습니다.
 
또한 매개변수가 많아지면 각각의 인수가 어떤 의미인지 파악하기 어렵거나, 매번 동일한 값을 전달해야 하는 불편함도 생깁니다.
C#에서는 이러한 상황에 활용할 수 있는 메서드 오버로딩, 가변 인수, 명명된 인수, 선택적 인수를 제공합니다.

메서드 오버로딩(Overloading)

메서드 오버로딩은 같은 이름으로 매개변수의 구성이 다른 여러 메서드를 정의하는 것입니다.
 
예를 들어 게임에서 캐릭터가 입힌 피해량을 출력하는 메서드를 작성한다고 가정해 보겠습니다.

static void ShowDamage(int damage)
{
    Console.WriteLine($"피해량 : {damage}");
}

현재는 피해량만 출력하고 있지만, 치명타가 발생했을 때는 치명타 여부도 함께 표시하고 싶다면 어떻게 해야 할까요?
 
별도의 이름을 가진 메서드를 만들어도 되지만, 오버로딩을 사용하면 같은 이름으로 여러 형태의 메서드를 정의할 수 있습니다.

static void ShowDamage(int damage)
{
    Console.WriteLine($"피해량 : {damage}");
}

static void ShowDamage(int damage, bool isCritical)
{
    if (isCritical)
        Console.WriteLine($"치명타 피해량 : {damage}");
    else
        Console.WriteLine($"피해량 : {damage}");
}

static void Main()
{
    ShowDamage(30);
    ShowDamage(50, true);
}

실행 결과

피해량 : 30
치명타 피해량 : 50

두 메서드는 모두 ShowDamage()라는 이름을 사용하지만 매개변수의 개수가 다릅니다.
 
따라서 컴파일러는 전달된 인수에 맞는 메서드를 선택하여 호출합니다.

오버로딩의 조건

C#에서는 매개변수의 개수나 자료형, 자료형의 순서 등이 다르면 메서드를 오버로딩할 수 있습니다.

static void Print(int a) { }

static void Print(string a) { }

static void Print(int a, int b) { }

static void Print(int a, string b) { }

static void Print(string a, int b) { }

위 메서드들은 모두 같은 이름을 사용하지만 매개변수 구성이 다르므로 오버로딩이 가능합니다.
반면 반환 형식이나 매개변수의 이름만 다르게 선언하는 것은 오버로딩으로 인정되지 않습니다.

static int Add(int a, int b)
{
    return a + b;
}

// 컴파일 오류 : 반환 형식만 다름
static float Add(int a, int b)
{
    return a + b;
}

컴파일러는 반환 형식만으로 두 메서드를 구분하지 않기 때문에 오류가 발생합니다.

오버로딩은 언제 사용할까?

오버로딩은 같은 목적의 기능을 서로 다른 매개변수 형태로 제공할 때 유용합니다.
 
예를 들어 캐릭터를 이동시키는 기능에서도 이동 거리만 전달하는 방법과 목표 좌표를 전달하는 방법을 각각 제공할 수 있습니다.
이때 서로 다른 이름을 사용하기보다는 Move()라는 하나의 이름으로 기능을 제공하는 것이 더 자연스러울 수 있습니다.
 
다만 오버로딩이라고 해서 반드시 내부 처리 방식이 달라야 하는 것은 아닙니다.
동일한 기능이더라도 매개변수 구성이 다른 여러 호출 방식을 제공하기 위해 사용할 수 있습니다.

가변 인수(params)

일반적인 메서드는 선언된 매개변수에 맞춰 인수를 전달해야 합니다.
 
예를 들어 여러 정수의 합계를 계산하는 메서드를 작성한다고 가정해 보겠습니다.

static int Sum(int a, int b)
{
    return a + b;
}

현재는 정수 두 개의 합계만 계산할 수 있습니다.
그런데 정수 3개, 4개 또는 그 이상의 합계를 계산해야 한다면 어떻게 해야 할까요?
 
오버로딩으로 매개변수의 개수가 다른 메서드를 계속 추가할 수도 있지만, 인수의 개수가 많아질수록 비효율적입니다.
이때 사용할 수 있는 것이 가변 인수(params)입니다.
 
params는 메서드에 전달하는 인수의 개수를 유연하게 처리할 수 있도록 하는 키워드입니다.

static int Sum(params int[] numbers)
{
    int result = 0;

    foreach (int number in numbers)
    {
        result += number;
    }

    return result;
}

static void Main()
{
    Console.WriteLine(Sum(10, 20));
    Console.WriteLine(Sum(10, 20, 30));
    Console.WriteLine(Sum(10, 20, 30, 40));
}

실행 결과

30
60
100

params int[] numbers는 전달받은 정수들을 배열로 다룰 수 있도록 합니다.
따라서 인수의 개수가 달라지더라도 별도의 메서드를 추가할 필요 없이 하나의 메서드로 처리할 수 있습니다.
 
또한 인수를 하나도 전달하지 않거나, 배열 자체를 전달하는 것도 가능합니다.

Sum();                         // 0
Sum(10);                       // 10
Sum(10, 20, 30);               // 60
Sum(new int[] { 10, 20, 30 }); // 60

여기서 params는 하나의 메서드에 한 번만 선언할 수 있으며, 반드시 마지막 매개변수에 위치해야 합니다.
참고로 최신 C#에서는 배열 외에 일부 컬렉션 형식도 params에 사용할 수 있지만, 여기서는 가장 기본적인 배열 방식을 기준으로 살펴보았습니다.
 
결국 가변 인수는 같은 종류의 데이터를 여러 개 전달해야 하는데, 그 개수가 일정하지 않을 때 유용하게 사용할 수 있습니다.

명명된 인수(Named Argument)

일반적으로 메서드를 호출할 때는 매개변수가 선언된 순서에 맞춰 인수를 전달합니다.

static void SpawnEnemy(string enemyType, int count, bool isBoss)
{
    Console.WriteLine($"적 종류 : {enemyType}");
    Console.WriteLine($"생성 수 : {count}");
    Console.WriteLine($"보스 여부 : {isBoss}");
}
SpawnEnemy("Goblin", 3, false);

위 코드는 "Goblin"이라는 적을 3마리 생성하고, 보스 여부를 false로 지정하는 호출입니다.
 
하지만 메서드 선언을 모르는 상태에서 호출 코드만 보면 3이나 false가 어떤 의미인지 바로 파악하기 어려울 수 있습니다.
이러한 상황에서 사용할 수 있는 것이 명명된 인수(Named Argument)입니다.
 
명명된 인수는 메서드를 호출할 때 매개변수의 이름을 직접 지정하여 값을 전달하는 방법입니다.

SpawnEnemy(enemyType: "Goblin", count: 3, isBoss: false);

이렇게 작성하면 각각의 인수가 어떤 매개변수에 전달되는지 쉽게 확인할 수 있습니다.
 
또한 매개변수의 이름을 지정했기 때문에 선언된 순서와 다르게 전달할 수도 있습니다.

SpawnEnemy(isBoss: false, enemyType: "Goblin", count: 3);

두 호출 모두 실행 결과는 동일합니다.

명명된 인수는 언제 사용할까?

명명된 인수는 매개변수가 많거나 전달하는 값의 의미를 명확하게 표현하고 싶을 때 유용합니다.
특히 bool 자료형은 단순히 true 또는 false를 전달하기 때문에 어떤 기능을 설정하는지 파악하기 어려울 수 있습니다.

SpawnEnemy("Goblin", 3, false);
SpawnEnemy("Goblin", count: 3, isBoss: false);

두 코드는 동일하게 동작하지만, 두 번째 코드는 전달하는 값의 의미를 더 명확하게 보여줍니다.
 
따라서 명명된 인수는 메서드의 내부 동작을 변경하기 위한 것이 아니라, 호출하는 코드의 가독성을 높이기 위한 기능이라고 이해하면 됩니다.

선택적 인수(Optional Argument)

앞서 작성한 SpawnEnemy()를 다시 살펴보겠습니다.

SpawnEnemy("Goblin", 1, false);
SpawnEnemy("Orc", 1, false);
SpawnEnemy("Dragon", 1, true);

현재는 적을 한 마리 생성하더라도 생성 수와 보스 여부를 매번 전달해야 합니다.
 
그런데 대부분의 경우 적을 한 마리씩 생성하고, 기본적으로 보스가 아니라고 가정한다면 매번 1과 false를 전달할 필요가 있을까요?
이때 사용할 수 있는 것이 선택적 인수(Optional Argument)입니다.
 
선택적 인수는 매개변수에 기본값을 지정하여 메서드를 호출할 때 해당 인수를 생략할 수 있도록 하는 기능입니다.

static void SpawnEnemy(
    string enemyType,
    int count = 1,
    bool isBoss = false)
{
    Console.WriteLine($"적 종류 : {enemyType}");
    Console.WriteLine($"생성 수 : {count}");
    Console.WriteLine($"보스 여부 : {isBoss}");
}

count에는 1, isBoss에는 false를 기본값으로 지정했습니다.
 
따라서 호출할 때 필요한 인수만 전달할 수 있습니다.

SpawnEnemy("Goblin");
SpawnEnemy("Orc", 3);
SpawnEnemy("Dragon", 1, true);

인수를 생략한 매개변수에는 선언할 때 지정한 기본값이 사용됩니다.
 
또한 앞서 살펴본 명명된 인수와 함께 사용할 수도 있습니다.

SpawnEnemy("Dragon", isBoss: true);

이 경우 count는 생략했으므로 기본값인 1이 사용되고, isBoss에는 true가 전달됩니다.

선택적 인수 사용 시 주의할 점

선택적 매개변수는 일반적인 필수 매개변수보다 뒤에 선언해야 합니다.

// 정상
static void Test(int a, int b = 10)
{
}

// 컴파일 오류
static void Test(int a = 10, int b)
{
}

또한 선택적 매개변수의 기본값에는 일반적으로 컴파일 시점에 결정할 수 있는 상수 값이나 default 표현식 등 허용된 값을 사용해야 합니다.
 
선택적 인수는 메서드의 동작 자체를 변경하기보다는 자주 사용하는 기본값을 지정하여 호출할 때 불필요한 인수를 생략하는 데 목적이 있습니다.

오버로딩과 선택적 인수의 차이

지금까지 살펴본 내용 중 오버로딩과 선택적 인수는 비슷한 기능을 구현할 수 있습니다.
예를 들어 적을 생성하는 메서드에서 생성 수를 생략하면 기본적으로 한 마리만 생성하도록 만들고 싶다고 가정해 보겠습니다.
 
오버로딩을 사용하는 경우

static void SpawnEnemy(string enemyType)
{
    SpawnEnemy(enemyType, 1);
}

static void SpawnEnemy(string enemyType, int count)
{
    Console.WriteLine($"{enemyType} : {count}마리");
}

오버로딩을 사용하면 매개변수 개수가 다른 두 개의 메서드를 정의할 수 있습니다.
반면 선택적 인수를 사용하면 다음과 같이 작성할 수 있습니다.
 
선택적 인수를 사용하는 경우

static void SpawnEnemy(string enemyType, int count = 1)
{
    Console.WriteLine($"{enemyType} : {count}마리");
}

두 방식 모두 다음과 같이 호출할 수 있습니다.

SpawnEnemy("Goblin");     // 1마리
SpawnEnemy("Goblin", 3);  // 3마리

결과는 동일하지만 구현 방법에는 차이가 있습니다.

구분 오버로딩 선택적 인수
정의 방식 여러 메서드 선언 매개변수에 기본값 지정
주요 목적 다양한 매개변수 구성 지원 일부 인수 생략
자료형이 다른 입력 처리 가능 기본값만으로는 해결 불가
기본값 제공 별도 오버로드로 구현 가능 직접 지정 가능

그렇다면 어떤 방식을 사용하는 것이 좋을까요?
 
단순히 기본값을 제공하려는 목적이라면 선택적 인수가 더 간결합니다.
반면 매개변수의 자료형이 다르거나, 호출 형태에 따라 별도의 처리가 필요한 경우에는 오버로딩이 적합합니다.
 
물론 오버로딩이라고 해서 반드시 내부 처리 방식이 달라야 하는 것은 아닙니다.
중요한 것은 메서드의 목적과 호출 방식을 고려하여 적절한 방법을 선택하는 것입니다.

정리

이번 글에서는 메서드 오버로딩과 다양한 인수 활용 방법에 대해 알아보았습니다.

구분 핵심 개념 활용 상황
오버로딩 같은 이름의 메서드를 여러 형태로 정의 매개변수 구성이 다른 경우
가변 인수 (params) 여러 인수를 하나의 매개변수로 전달 인수 개수가 일정하지 않은 경우
명명된 인수 매개변수 이름을 지정하여 전달 전달값의 의미를 명확하게 표현할 때
선택적 인수 기본값을 지정하여 인수 생략 일부 인수에 기본값이 필요한 경우

네 가지 기능은 모두 메서드를 보다 편리하게 정의하거나 호출하기 위한 방법이지만, 각각의 목적에는 차이가 있습니다.
 
오버로딩은 다양한 입력 형태를 지원하고, 가변 인수는 일정하지 않은 개수의 인수를 처리합니다.
반면 명명된 인수는 호출하는 코드의 가독성을 높이고, 선택적 인수는 기본값을 통해 불필요한 인수 전달을 줄여줍니다.
 
따라서 단순히 문법을 외우기보다는 각 기능이 어떤 문제를 해결하기 위해 사용되는지 이해하는 것이 중요합니다.
이러한 차이를 이해하고 상황에 맞게 활용한다면 코드의 중복을 줄이고, 메서드의 역할과 호출 의도를 더욱 명확하게 표현할 수 있을 것입니다.

반응형