CodeGym /행동 /C# SELF /메서드 오버로딩 (Method Overloading)

메서드 오버로딩 (Method Overloading)

C# SELF
레벨 21 , 레슨 1
사용 가능

1. 소개

실생활에서 많은 행동들은 마치 다기능 스위스 나이프 같아: 똑같은 명령이지만 여러 도구 세트로 동작할 수 있지. 예를 들어, ATM을 생각해봐: 카드를 넣으면 ATM이 비밀번호를 물어보고, 전화번호를 입력하면 SMS로 온 인증코드를 기다려. 행동은 하나 — "사용자 확인", 하지만 방식은 다르지.

프로그래밍에서도 비슷한 상황을 자주 만나: 뭔가 한 가지 작업을 해야 하는데, 데이터 타입이나 파라미터 개수가 다를 수 있어. 예를 들어, 우리 메서드는 사람이나 동물 모두에게 인사를 해야 하거나, 두 개, 세 개, 아니면 열 개의 정수를 더해야 할 수도 있지.

물론, 메서드 이름을 다르게 지을 수도 있어: SumTwo, SumThree, SumArray 이런 식으로. 근데 개발자들은 귀찮은 걸 싫어해 (괜히 "귀찮음이 진보의 원동력"이라는 말이 있는 게 아니지). 게다가 이렇게 하면 코드가 덜 읽기 쉬워져.

메서드 오버로딩

메서드 오버로딩은 같은 이름의 메서드가 서로 다른 파라미터 세트로 동작하게 하는 방법이야. 메서드 이름은 그대로 두고 파라미터만 다르게 하는 거지. 이건 다형성(polymorphism)의 한 형태지만, 상속이랑은 관련 없어.

오버로딩이란 한 클래스(혹은 구조체) 안에서 같은 이름의 여러 메서드를 만들 수 있게 해주는 거야. 단, 파라미터의 타입, 개수, 순서가 달라야 해.

메서드 시그니처

시그니처란 C#에서 메서드 이름 + 파라미터 타입(들) + 순서를 말해. 메서드의 반환 타입은 시그니처에 포함되지 않아! 이거 때문에 종종 예상치 못한 에러가 나기도 해 (조금 뒤에 설명할게).

2. 오버로딩 실전: 간단한 예제

이제 Greeter라는 클래스를 만들어서, 이름만으로, 이름과 나이로, 혹은 아예 파라미터 없이 유저를 다르게 인사해보자.


public class Greeter
{
    // 파라미터 없는 인사
    public void Greet()
    {
        Console.WriteLine("안녕, 세상!");
    }

    // 이름으로 인사
    public void Greet(string name)
    {
        Console.WriteLine($"안녕, {name}!");
    }

    // 이름과 나이로 인사
    public void Greet(string name, int age)
    {
        Console.WriteLine($"안녕, {name}! 벌써 {age}살이야? 멋지다!");
    }
}

이제 이 메서드들 중 아무거나 호출하면, C# 컴파일러가 파라미터 개수와 타입을 보고 알아서 맞는 버전을 골라줄 거야.

var greeter = new Greeter();
greeter.Greet();                // 안녕, 세상!
greeter.Greet("아냐");           // 안녕, 아냐!
greeter.Greet("표트르", 23);      // 안녕, 표트르! 벌써 23살이야? 멋지다!

3. 파라미터 타입과 개수로 구분하기

오버로딩은 메서드가 다음과 같이 다를 때 동작해:

  • 파라미터 개수,
  • 파라미터 중 하나 이상의 타입,
  • 파라미터 타입의 순서 (이건 좀 조심해야 해).

이번엔 나이만 받는 오버로딩을 추가해보자:


public void Greet(int age)
{
    Console.WriteLine($"이 나이, 멋지다! ({age}살)");
}

이제 호출해보면:

greeter.Greet(10);          // 이 나이, 멋지다! (10살)

중요: 메서드가 반환 타입만 다르고 파라미터가 같으면 오버로딩 불가야. 예를 들어, 이런 코드는 에러가 나:


// 컴파일 에러!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
반환 타입만 다른 오버로딩은 불가능!

컴파일러가 이렇게 말할 거야: "이미 Foo(string) 메서드가 있는데, 좀 더 복잡하게 해봐!"

4. 오버로딩과 C# 표준 라이브러리

오버로딩은 우리가 만든 Greet만 있는 게 아니야. .NET의 Console.WriteLine 문서를 열어보자:

시그니처 설명
WriteLine()
빈 줄 출력
WriteLine(string)
문자열 출력
WriteLine(int)
정수 출력
WriteLine(double)
실수 출력
WriteLine(string, object)
문자열을 하나의 인자로 포맷
WriteLine(string, params object[])
여러 인자로 포맷

이게 다 같은 WriteLine 메서드의 오버로딩이야. 그래서 항상 이렇게 쓸 수 있는 거지:

Console.WriteLine("그냥 문자열");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("합계: {0}", 42);

그리고 컴파일러가 항상 네 호출을 제대로 해석해!

5. 컴파일러는 어떤 오버로딩을 호출할까?

여기선 엄격해: 실제로 넘긴 인자의 타입과 개수를 본다. 표로 보면:

호출 어떤 버전이 실행될까?
greeter.Greet()
void Greet()
greeter.Greet(75)
void Greet(int)

애매할 땐 어떻게 될까?

가끔 상황이 꼬일 때가 있어. 예를 들어, 컴파일러가 어떤 버전을 골라야 할지 모르는 애매한 오버로딩:


public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }

printer.Print(5, 10); 
// 에러: 애매함 — 어떤 Print를 호출해야 할까? (둘 다 될 것 같음)

컴파일러가 애매하다고 에러를 낼 거야. 이런 경우엔 파라미터 개수나 타입이 비슷한 오버로딩은 피하는 게 좋아, 컴파일러가 헷갈릴 수 있으니까.

6. params — 파라미터 개수 가변

숫자를 몇 개가 될지 모르게 받고 싶을 때가 있지? 그럴 땐 params 키워드를 쓰면 돼.


public void SumAll(params int[] numbers)
{
    int sum = 0;
    foreach (int n in numbers)
        sum += n;
    Console.WriteLine($"합계: {sum}");
}
가변 파라미터 메서드 ( params)

이제 이렇게 호출할 수 있어:

SumAll(1, 2, 3);           // 합계: 6
SumAll(10, 20);            // 합계: 30
SumAll();                  // 합계: 0

params가 붙은 메서드는 오버로딩이랑 같이 쓸 수 있는데, 컴파일러가 어떤 버전을 골라야 할지 헷갈릴 만한 오버로딩은 피하는 게 좋아.

7. 오버로딩과 파라미터 한정자 (ref, out, in)

C#은 파라미터 한정자에 따라 메서드를 구분해 (즉, void Foo(int a)void Foo(ref int a)는 시그니처가 달라서 둘 다 같은 클래스에 있을 수 있어):

public void SetValue(int a)
{
    a = 42;
}

public void SetValue(ref int a)
{
    a = 100;
}
ref 한정자로 오버로딩

ref 없이 호출하면 첫 번째 버전, ref로 호출하면 두 번째 버전이 실행돼:

int n = 5;
SetValue(n);     // n은 5 그대로 (값이 복사됨)
SetValue(ref n); // n은 100이 됨

8. 도식: 오버로딩이란?


      +----------+
      |  MyClass |
      +----------+
           |
           |            (메서드 일부)
    +-----------------------+
    |   void Foo()          |
    |   void Foo(int a)     |
    |   void Foo(string s)  |
    |   void Foo(int a, int b) |
    +-----------------------+
클래스에서 메서드 오버로딩 도식

코드로 표현하면:

// 오버로딩된 Foo() 메서드 호출:
var mc = new MyClass();
mc.Foo();              // void Foo()
mc.Foo(5);             // void Foo(int)
mc.Foo("Hello");       // void Foo(string)
mc.Foo(2, 3);          // void Foo(int, int)

9. 예제: 우리 앱에 메서드 오버로딩 추가

우리 학습용 앱을 더 발전시켜서, 동물 계층에 메서드 오버로딩을 추가해보자.


public class Animal
{
    public string Name { get; set; }

    // 소리 내기 메서드
    public virtual void MakeSound()
    {
        Console.WriteLine("이상한 소리...");
    }

    // 오버로딩: 볼륨 지정해서 소리 내기
    public void MakeSound(int volume)
    {
        Console.WriteLine($"동물이 {volume} dB로 소리 낸다.");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("멍!");
    }

    // 오버로딩: 볼륨 지정해서 멍멍
    public void MakeSound(int volume)
    {
        Console.WriteLine($"멍! (볼륨: {volume} dB)");
    }
}

이렇게 호출해봐:

Dog rex = new Dog();
rex.MakeSound();           // 멍!
rex.MakeSound(75);         // 멍! (볼륨: 75 dB)

주의: 자식 클래스(Dog)에서 MakeSound(int volume) 메서드를 오버로딩했으니, 이제 파라미터 있는 버전과 없는 버전 둘 다 쓸 수 있어.

10. 메서드 오버로딩에서 흔한 실수

실수 1: 반환 타입만 다르게 오버로딩하려고 함.
이건 불가능해 — 반환 타입은 시그니처에 포함되지 않아. 오버로딩은 입력 파라미터의 개수나 타입이 달라야 하고, voidint 같은 반환 타입으론 구분 못 해.

실수 2: 컴파일러를 헷갈리게 하는 애매한 오버로딩.
파라미터 개수는 같고 타입만 비슷한 오버로딩(intdouble 등)은 컴파일러를 혼란스럽게 할 수 있어. 예: Print(int a, double b)Print(double a, int b)Print(1, 1) 호출 시 애매하다고 에러가 남.

실수 3: params와 다른 오버로딩이 충돌.
params가 붙은 메서드는 다른 오버로딩을 가로챌 수 있어. 타입이 같으면 컴파일러가 네가 기대한 게 아닌 버전을 고를 수도 있음.

실수 4: refout이 시그니처에 포함된다는 걸 잊음.
Do(ref int x)Do(out int x)는 서로 다른 오버로딩이야. 이걸 신경 안 쓰면 잘못된 버전을 호출할 수 있어.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION