SW개발

[C# 기초강좌] 11. C# 오버로딩과 오버라이딩 그리고 new

박종명 2023. 11. 2. 08:40
728x90
이 글은 제가 과거에 운영했던 사이트인 http://dotnet.mkexdev.net 의 글을 옮겨온 것입니다. 원본 글은 2010년에 작성되었습니다.

그 전에 운영했었던 사이트(mkex.pe.kr)은 흔적도 없이 사라 졌습니다. 그속의 글들도 모두... 그래서 이 사이트도 사라지기 전에 옮기고 싶은 글을 조금씩 이 블로그로 이동시키려 합니다.
(원본글) http://dotnet.mkexdev.net/Article/Content.aspx?parentCategoryID=1&categoryID=5&ID=675

이 글은 닷넷 기초 지식을 전파하기 위해 2010경에 작성되었으며, 당시 윤성우의 프로그래밍 스터디그룹 네이버 카페에도 필진으로 참여하여 연재했던 글이기도 합니다. 현재 시점에서 조금 달라진 부분이 있을 수 있으나 기본 원리와 언어 기초에 해당하는 부분은 크게 변하지 않았을 것으로 생각하며 이런 부분들을 감안해서 봐 주시기 바랍니다.

 

메서드 재정의 3형제: 오버로딩(overloading), 오버라이딩(overriding), 그리고 뉴(new)”

 

안녕하세요. 박종명입니다. 닷넷 열한 번째 강좌를 진행하도록 하겠습니다.
이번 강좌는 C#에서 메서드 정의 기법인 오버로딩과 오버라이딩 그리고 new 에 대해 알아 보겠습니다.

이 세 가지 기법은 기존에 정의되어 있는 메서드와 동일한 이름으로 추가 및 재 정의하는 기법으로써, 그 필요성과 용례 및 문법적 규칙은 조금씩 다르지만 큰 의미에서 메서드를 재정의 기법이라 할 수 있겠습니다


 

오버로딩(Overloading)

일반적으로 하나의 클래스 안에 정의된 메서드들의 이름은 중복될 수 없습니다.

그러나 오버로딩 기법을 이용하면 하나의 클래스에 같은 이름을 가진 메서드를 여러 개 정의할 수 있습니다.

 

다만 메서드의 이름은 같되 매개변수의 정보(개수 및 타입)는 달라야 합니다.

메서드의 이름과 입력 매개변수의 정보(개수 및 타입)를 메서드의 시그너처라고 합니다.

 

: 메서드 시그너처(Signature): 메서드 이름 + 매개변수 정보(개수 및 타입 정보)

 

한 클래스에 정의된 메서드들은 모두 시그너처 정보가 유일해야 합니다. 즉 메서드의 시그너처를 유일하게 한다면 메서드의 이름은 얼마든지 같을 수 있는 것입니다. 결국 오버로딩 기법은 이미 정의된 메서드와 이름은 같지만 시그너처는 다르기 때문에 가능한 것입니다

 

다음 코드는 하나의 클래스의 동일한 이름의 메서드(그러나 시그너처는 다른)가 정의된 모습입니다.

 

public class MyClass{

   public int MyMethod(){

       return 0;

   }

   public int MyMethod(int i){

       return i;

   }

}

 

 

오버로딩은 왜 사용하나?

오버로딩 기법을 사용하지 않고도 프로그램을 완성할 수 있습니다.

오버로딩이 유용한 경우는 경험에 의해 그리고 감각적으로 알 수 있어 여러 사례가 있겠습니다만저는 이런 경우를 생각해 보았습니다

 

동일하거나 유사한 일을 수행하는 메서드가, 전달 받는 매개변수에 따라 조금씩 다른 연산을 해야 하는 경우에 모든 상황에 따라 메서드 이름을 각각 정의 하는 것보다 매개변수 정보만 달리하여 동일한 이름으로 정의한다면 코드를 작성하는 입장에서나 사용하는 입장에서 모두 보다 직관적이고 편리하게 사용할 수 있을 것입니다.

 

닷넷 프레임워크의 라이브러리에서도 이러한 필요성에 의한 메서드 오버로딩 예를 쉽게 찾아 볼 수 있습니다.

예를 들어 FileInfo 클래스에 Open 메서드는 다음과 같이 3개로 오버로딩 되어 있습니다

 

- FileInfo.Open(FileMode)

- FileInfo.Open(FileMode, FileAccess)

- FileInfo.Open(FileMode, FileAccess, FileShare)

 

즉 파일 열기를 할 때 모드만 지정하거나 읽기/쓰기 권한을 같이 지정하거나 공유 권한을 모두 지정할 수 있도록 하는 것입니다. 만일 FileMode만 지정한다면 나머지 값들은 미리 정의된 기본 값으로 지정됩니다.

 

대체로 이 기본 값은 일반적으로 가장 많이 쓰이는 값이기 때문에 개발자는 모든 상황을 고려할 필요 없이 필요한 정보만 전달하면 되는 것입니다.

 

물론 상세한 설정을 원하면 얼마든지 가능하구요.

마치 프로그램 설치 시 기본 설정과 상세(사용자 정의) 설정이 있는 것과 유사해 보입니다.

 

메서드 오버로딩이 지원되지 않는다면 아마도 아래와 같이 메서드 이름을 각기 달리하여 구현해야 할 것입니다.

아래는 제가 임의로 명명한 예입니다

 

- FileInfo.Open(FileMode)

- FileInfo.OpenWithFileAccess(FileMode, FileAccess)

- FileInfo.OpenWithFileAccessAndFileShare(FileMode, FileAccess, FileShare)

 

매개변수가 더 많을 경우에는 더 복잡해 지겠죠.

 

아래의 예는 큰 수를 반환하는 메서드를 전달받는 매개변수의 수에 따라 오버로딩 한 예시입니다.

 

public class MyClass{

   public static int Max(int i, int j){

       return i > j ? i : j;     //둘 중 큰수를 반환한다

   }

   public static int Max(int i, int j, int k){

       return Max(Max(i, j), k); //Max(int, int) 를 호출한다

   }

   public static int Max(int i, int j, int k, int l){

       return Max(Max(i, j, k), l); //Max(int, int, int) 를 호출한다

   }

   public static int Max(int[] arrays){

       int maxValue = 0;

       foreach (int i in arrays){

           maxValue = Max(maxValue, i); //Max(int,int) 를 호출한다

       }

       return maxValue;

   }

}

 

  

오버라이딩(Overriding)

앞서 살펴본 오버로딩(Overloading)은 한 클래스 내에서 동일한 이름의 메서드를 추가 정의하는 것인 반면오버라이딩(Overriding)은 클래스간 상속 관계에서 메서드를 재 정의하는 기법입니다

 

두 클래스가 상속 관계에 있으면 부모 클래스의 public, protected 으로 선언된 메서드는 자식 클래스로 그대로 상속되어 자식클래스에서 사용이 가능하게 됩니다. 즉 이미 정의된 메서드를 재 사용하게 되는 것이죠.

 

이 때 단순히 재 사용을 하지 않고 자식 클래스에서 상속 받은 메서드를 재 정의하여 다른 연산을 수행하도록 하는 것이 오버라이딩 입니다. 즉 자식 클래스에서 부모 클래스의 특정 메서드를 다시 정의하게 됩니다.

 

오버라이딩은 오버로딩과는 달리 메서드의 이름이 동일해야 합니다.

그리고 virtual  override 키워드를 사용하여 오버라이딩을 구현하게 됩니다.

 

 

virtual  override 키워드

메서드를 오버라이딩 할 때 부모 클래스에서는 virtual 로 자식 클래스에서는 override 키워드로 메서드를 정의해야 합니다. 즉 부모 클래스에서는 자식 클래스에서 오버라이딩이 가능하도록 가상(virtual) 메서드로 정의해야 하고 자식 클래스에서는 이 가상메서드를 override 키워드로 재정의 하게 됩니다

 

다음과 같이 Parent 클래스를 상속받는 Child 클래스가 있을 경우, public 메서드는 자동으로 상속 됩니다.

 

class Parent{

    public virtual void ParentMethod(){

       Console.WriteLine("부모 메서드");

    }

}

 

class Child : Parent{}

 

Child 클래스에는 ParentMethod 가 정의되어 있지 않지만 부모로부터 상속받아서 사용 가능하죠.

Child child = new Child();

child.ParentMethod(); //"부모메서드" 가 출력 됨

 

이때 부모 클래스의 ParentMethod를 자식 클래스에서는 다르게 구현 하고 싶을 경우 오버라이딩 하면 됩니다.

class Child : Parent{

   public override void ParentMethod(){

       Console.WriteLine("자식메서드(오버라이드 됨)");

   }

}

 

오버라이딩 된 상태에서 자식 객체로 해당 메서드를 호출하면 재 정의한 메서드가 호출되는 것입니다.

Child child = new Child();

child.ParentMethod();//"자식메서드" 가 출력 됨

 

자바와는 달리 부모 클래스에서 virtual로 선언하지 않으면 오버라이딩을 할 수 없다는 것에 주의하세요.

(자바는 모든 메서드가 기본적으로 virtual 이죠?)

 

참고로 virtual 이외에 abstract (추상 메서드) 로 정의되어 있는 메서드 역시 오버라이딩 가능합니다.

 

 

오버라이딩의 핵심은 다형성(Polymorphism)

객체 지향의 중요한 개념 중에 하나가 바로 다형성 입니다.

다형성은 같은 메시지에 다른 동작이 가능하게 하는 객체 지향의 특성인데요.

이러한 다형적인 동작을 가능토록 구현하기 위해 오버라이딩 기법을 사용하게 됩니다.

 

예를 하나 들어 보죠

전 국민의 게임인 스타크래프트(StarCraft)를 개발한다고 가정해 봅니다.

게임에는 많은 유닛이 존재하고 이 유닛들의 공격 형태를 서로 다릅니다.

 

예를 들어 무대포 질럿은 양 팔에 달린 칼로 공격하고 우직한 드래곤은 꽁알탄 같은 총알을 쏘면서 공격합니다.

이외 다른 유닛들도 공격의 형태가 조금씩 다릅니다.

 

그리고 공격을 할 때 개별 유닛에게 명령을 내릴 수도 있지만 전체를 모아서 한 번에 공격 명령을 내릴 수도 있습니다. 즉 마우스로 쭈~욱 끌어서 많은 유닛을 선택해서 공격하는 것입니다, 물론 이 때 선택된 유닛들은 공격형태가 서로 다른 것끼리 짬뽕되어 있습니다

 

이 경우 부모 클래스에 Attack 라는 메서드를 정의 하고 각 유닛 클래스에서는 이 메서드를 오버라이딩 하여 같은 명령에 서로 다른 동작을 하도록 구현하고 싶습니다.

 

즉 모든 유닛에 한번의 Attack 명령으로 다형적인 동작이 가능토록 하는 것입니다. 코드를 보겠습니다

 

abstract class Unit //모든 유닛이 상속받게 되는 부모 클래스

{

   public abstract void Attack();

}

 

class Zelot : Unit{

   public override void Attack(){

       Console.WriteLine("사시미 찔러 대기");

   }

}

 

class Dragon : Unit{

   public override void Attack(){

       Console.WriteLine("꽁알탄 쏘아 대기");

   }

}

 

이렇게 클래스를 구현하고 이제 공격 명령을 내려 보도록 하겠습니다.

질럿 두 마리와 드래곤 한 마리가 선택되었다고 가정하고 한번에 공격 명령을 내립니다.

 

 

static void Main(string[] args){

       //그룹 공격(질럿 두마리, 드래곤 한 마리)

       Unit[] unitArray = new Unit[] { new Zelot(), new Zelot(), new Dragon() };

 

       foreach (Unit unit in unitArray){

          //모든 유닛에 Attack라는 같은 메시지를 호출하여 다른 행위를 하도록 한다

           unit.Attack();

       }        

}

 

그리고 결과는 다음과 같습니다

 

모든 유닛에 Attack라는 동일한 메서드를 호출하지만 각 유닛의 공격 행동은 서로 다릅니다.

이렇듯 오버라이딩은 객체의 다형적인 동작을 가능토록 하는 객체 지향 기법입니다.

 

물론 오버라이딩을 다형성만을 위해서 사용하지는 않습니다.

어떤 경우든 부모 클래스의 virtual 메서드를 재 정의 하고 싶을 경우 사용하면 되는 것입니다.

 

참고로 오버라이딩은 많은 객체 지향 디자인 패턴에 단골로 사용되는 기법이라 하겠습니다.

대부분의 디자인패턴에서 오버라이딩의 사례를 쉽게 찾아 볼 수 있는 만큼 중요하고도 실용적인 기법이니 개념을 정확히 알고 있으시길 바랍니다.

 

 

new

마지막으로 new 를 통한 메서드 재 정의 하는 것을 알아 보겠습니다.

new 역시 오버라이딩처럼 상속과 연관이 있습니다.

 

다만 new 를 통해 메서드를 재 정의하게 되면 부모 클래스의 원 메서드는 숨기게(가려지게) 되는 차이가 있습니다.

말 그대로 메서드를 새로(new) 정의하는 기법입니다. 부모 클래스의 메서드를 사용하지 않고 새로 정의하는 것이죠.

코드를 보겠습니다

 

public class Parent{

   public void ParentMethod(){

       Console.WriteLine("부모 메서드");

   }

}

 

public class Child : Parent{

   public new void ParentMethod(){

       Console.WriteLine("자식메서드(새로 정의됨)");

   }

}

 

new 는 부모 메서드가 virtual 이거나 아니거나 상관이 없습니다.

 

 

new 와 오버라이딩의 차이점

언뜻 보면 오버라이딩과 별반 달라 보이지 않습니다. 그러나 이 둘은 엄연히 다른 기법입니다.

오버라이딩과는 달리 new 를 통한 메서드 재정의는 다형성과 아무런 연관이 없습니다.

 

C#에서 상속 관계인 두 클래스에서 상위 타입으로의 형 변환은 묵시적으로 이루어 지기 때문에 다음과 같은 코드 작성이 가능합니다

 

Parent myObject = new Child();

myObject.ParentMethod();

 

이 때 출력 결과는 어떻게 될까요?

오버라이딩을 했을 경우에는 자식 클래스(Child)의 오버라이드 된 메서드가 실행되는 반면new 로 재정의 할 경우에는 부모 클래스(Parent)의 원 메서드가 실행됩니다.

 

new 는 다형성과는 아무런 연관이 없이 단순히 자식 클래스에서 새로이 정의한다는 것입니다.

 

다시 말해 오버라이딩은 부모 타입으로 메서드를 호출하더라도 객체로 생성된 자식 타입의 메서드가 호출되도록 하여 객체의 다형성이 가능해 지는 것입니다.

 

이것으로 메서드 재 정의 3형제에 대한 소개를 마치도록 하겠습니다.

오버라이딩과 오버로딩은 실무에서 굉장히 많이 사용되는 기법입니다.

수 많은 코드에서 관련 예를 쉽게 찾아 볼 수 있을 것입니다. 그런 만큼 정확한 개념을 숙지하시기를 바랍니다.

 

그럼.. 즐거운 주말 되세요~~