이름은 같고 투입구만 다르다
Esc

    이름은 같고 투입구만 다르다

    –C#기초

    지금까지 아무 생각 없이 써 온 것이 있습니다.

    Console.WriteLine(42);
    Console.WriteLine("안녕");
    Console.WriteLine(3.14);
    Console.WriteLine(true);

    넣는 값의 타입이 전부 다른데 이름은 하나입니다. 메서드는 매개변수 타입을 정해서 만든다고 했는데, 어떻게 다 받을까요.

    답은 간단합니다. WriteLine 이 한 개가 아닙니다. 숫자를 받는 것, 문자열을 받는 것, 실수를 받는 것이 따로 있고 이름만 같습니다.

    하는 일이 같으면 이름도 같아야 읽힙니다. 받는 것만 다르게 여러 벌 만듭니다.

    이걸 오버로딩이라고 합니다.

    이름이 하나여야 읽힌다

    오버로딩이 없으면 이렇게 됐을 겁니다.

    Console.WriteLineInt(42);
    Console.WriteLineString("안녕");
    Console.WriteLineDouble(3.14);

    쓰는 쪽이 매번 “지금 넣는 게 무슨 타입이더라” 를 생각해야 합니다. 타입을 바꾸면 메서드 이름도 같이 고쳐야 합니다.

    하는 일이 같은데 이름이 다르면 부담은 쓰는 쪽이 집니다. 오버로딩은 그 부담을 만드는 쪽으로 옮깁니다.

    void Print(int value)
    {
        Console.WriteLine("숫자: " + value);
    }
    
    void Print(string value)
    {
        Console.WriteLine("글자: " + value);
    }
    
    Print(42);       // 숫자: 42
    Print("안녕");    // 글자: 안녕

    컴파일러가 넣은 값의 타입을 보고 어느 쪽을 부를지 고릅니다. 부를 때 정해지는 것이 아니라 컴파일할 때 정해집니다.

    무엇을 보고 고르나

    컴파일러가 보는 것은 매개변수의 타입, 개수, 순서 셋뿐입니다.

    void Print(int a)              // 타입이 다르다
    void Print(string a)
    
    void Print(int a)              // 개수가 다르다
    void Print(int a, int b)
    
    void Print(int a, string b)    // 순서가 다르다
    void Print(string a, int b)

    전부 됩니다. 셋 중 하나라도 다르면 다른 메서드로 봅니다.

    반환 타입은 보지 않습니다.

    int GetValue()
    string GetValue()      // 컴파일 에러

    왜 안 되는지는 이렇게 생각하면 됩니다.

    GetValue();

    이 줄만 보고 어느 쪽을 불러야 할지 정할 수가 없습니다. 돌려받은 값을 안 쓰는 코드도 되니까요. 고르는 근거는 넘기는 것에만 있어야 합니다. 매개변수가 완전히 같으면 이름을 다르게 지어야 합니다.

    매개변수 이름도 안 봅니다.

    void Print(int score)
    void Print(int point)      // 컴파일 에러

    앞에서 매개변수 이름은 메서드 안에서만 통한다고 했습니다. 밖에서는 안 보이는 이름이니 고르는 근거가 될 수 없습니다.

    기본값을 정해 두면 생략할 수 있다

    거의 같은데 매개변수 하나만 있고 없고인 경우가 있습니다.

    void Greet(string name)
    {
        Greet(name, "안녕하세요");
    }
    
    void Greet(string name, string message)
    {
        Console.WriteLine(name + "님, " + message);
    }

    두 벌을 만들 것 없이 기본값을 적어 두면 됩니다.

    void Greet(string name, string message = "안녕하세요")
    {
        Console.WriteLine(name + "님, " + message);
    }
    
    Greet("서병필");                  // 서병필님, 안녕하세요
    Greet("서병필", "오랜만입니다");   // 서병필님, 오랜만입니다

    규칙이 하나 있습니다. 기본값이 있는 매개변수는 목록 맨 뒤에 와야 합니다.

    void Greet(string message = "안녕", string name)    // 컴파일 에러

    앞에 있으면 Greet("서병필") 이 어느 자리에 넣은 것인지 알 수 없습니다. 넣은 값은 앞에서부터 차례로 채워지기 때문입니다.

    이름을 붙이면 순서를 안 지켜도 된다

    기본값이 여러 개일 때 문제가 생깁니다.

    void Setup(int width = 800, int height = 600, bool fullscreen = false)

    fullscreen 만 바꾸고 싶은데, 앞에서부터 채워지니 앞의 두 개를 다 적어야 합니다.

    Setup(800, 600, true);      // 안 바꿀 값을 굳이 적었습니다

    이럴 때 이름을 붙여서 넘깁니다.

    Setup(fullscreen: true);

    컴파일러가 이름을 보고 어느 자리인지 압니다. 순서를 지킬 필요도 없습니다.

    Setup(height: 1080, width: 1920);

    이름을 붙이면 읽기도 좋아집니다.

    CreateWindow(1920, 1080, true, false);
    CreateWindow(width: 1920, height: 1080, fullscreen: true, resizable: false);

    아래쪽은 주석 없이도 읽힙니다. true 나 false 를 여러 개 넘길 때는 이름을 붙이는 편이 낫습니다. 숫자만 봐서는 무엇을 켜고 끄는지 알 수 없습니다.

    어디까지 묶을 것인가

    오버로딩은 하는 일이 같을 때 씁니다. 이름이 같으니 쓰는 사람은 같은 일을 기대합니다.

    double Average(int a, int b)
    double Average(List<int> nums)

    이건 좋습니다. 둘 다 평균을 구하고, 재료만 다릅니다.

    void Save(string path)        // 파일에 저장
    void Save(int userId)         // 서버에 전송

    이건 이름을 나눠야 합니다. 하는 일이 다릅니다. 같은 이름이 붙어 있으면 읽는 사람이 같은 일이라고 믿습니다. 오버로딩은 문법이 허락하는 범위가 아니라 읽는 사람의 기대에 맞춰 씁니다.

    정리

    • 이름이 같고 매개변수가 다른 메서드를 여러 벌 만드는 것이 오버로딩입니다.
    • 컴파일러가 넘긴 값의 타입, 개수, 순서를 보고 고릅니다. 컴파일할 때 정해집니다.
    • 반환 타입과 매개변수 이름은 고르는 근거가 아닙니다. 그것만 다르면 컴파일 에러입니다.
    • 매개변수 하나만 있고 없고라면 오버로딩 대신 기본값을 씁니다.
    • 기본값이 있는 매개변수는 목록 맨 뒤에 와야 합니다.
    • 이름을 붙여 넘기면 순서를 안 지켜도 되고, 읽기도 좋아집니다.
    • 하는 일이 다르면 이름을 나눕니다. 같은 이름은 같은 일이라는 약속입니다.