메서드 오버라이드(Override)

  • 19 minutes to read

부모 클래스에 만들어진 메서드를 자식 클래스에서 다시 새롭게 만들어 사용하는 것을 메서드 오버라이드(override)라고 합니다. 여기서는 메서드 오버라이드의 여러 가지 기능을 자세히 알아보겠습니다.

> // 오버라이드: 부모 클래스의 메서드를 자식 클래스에서 재정의(다시 정의)해서 사용하기
TIP

오버라이드(override) 영어 단어는 기각하다, 무시하다, 뒤엎다 등의 뜻을 가집니다. 프로그래밍에서는 부모의 기능을 무시하고 자식에서 새로운 기능으로 다시 정의하는 의미를 가집니다.

메서드 오버라이드(Override): 재정의

클래스 간의 관계를 따지는 상속(inheritance) 개념에서 부모 클래스에 이미 만들어져 있는 메서드를 동일한 이름으로 자식 클래스에서 다시 정의(재정의)해서 사용한다는 개념이 메서드 오버라이드입니다.

다음은 메서드 오버라이드에 대한 추가 설명입니다. 간단히 읽고 넘어가세요.

  • 메서드 오버라이드는 메서드를 새롭게 정의하는 것을 말합니다.
  • 오버라이드(override), 오버라이딩(overriding)은 동일한 표현입니다.
  • 부모 클래스에 virtual 키워드로 선언해 놓은 메서드는 자식 클래스에서 override 키워드를 사용해서 재정의해서 사용 가능합니다. virtual, override는 짝궁 키워드입니다.

상속 관계에서의 메서드 오버라이드

C#에서 override 키워드는 "재정의" 또는 "다시 정의"를 의미합니다. 상속 관계에서의 메서드를 재정의해서 사용하는 3가지 방법을 코드로 정리해보겠습니다.

코드: InheritanceMethodOverride.cs

> public class Parent
. {
.     public void Say() => Console.WriteLine("부모_안녕하세요.");
.     public void Run() => Console.WriteLine("부모_달리다.");
.     public virtual void Walk() => Console.WriteLine("부모_걷다.");
. }
> 
> public class Child : Parent
. {
.     public void Say() => Console.WriteLine("자식_안녕하세요.");
.     public new void Run() => Console.WriteLine("자식_달리다.");
.     public override void Walk() => Console.WriteLine("자식_걷다.");
. }
> 
> // 메서드 오버라이드(override): 함수 재정의
> Child c = new Child();
> c.Say(); //[1] 재사용
자식_안녕하세요.
> c.Run(); //[2] x -> new
자식_달리다.
> c.Walk(); //[3] virtual -> override
자식_걷다.

부모 클래스의 Say() 메서드는 자식 메서드에서 다시 정의하지만 어떠한 표시도 하지 않고 그대로 사용합니다. 이렇게 사용해도 실행에는 문제는 없지만, Visual Studio의 코드 편집기에서는 new 키워드를 붙여서 사용하라는 경고 메시지가 추가로 표시됩니다.

Run() 메서드는 new 키워드를 사용해서 명확하게 자식 클래스에서 부모 클래스를 재정의해서 사용하겠다는 표시를 합니다.

Walk() 메서드는 부모 클래스에서 상속해서 사용해도 된다는 의미로 virtual을 붙이고 자식 클래스에서는 재정의해서 쓰겠다는 의미로 override를 붙여서 재정의하는 가장 좋은 모양의 코드를 볼 수 있습니다.

참고로 부모에 붙이는 virtual 키워드는 Visual Basic 언어의 Overridable의 의미를 갖습니다.

메서드 오버로드와 오버라이드

처음 프로그래밍을 할 때 쉽게 혼동되는 단어가 오버로드(Overload)와 오버라이드(Override)입니다. 오버로드는 여러 번 정의이고 오버라이드는 다시 정의(재정의)입니다.

이미 많이 사용해 본 함수 중복을 의미하는 메서드 오버로드 예제를 하나 살펴볼까요?

코드: MethodOverloading.cs

> static void Print(int number) => Console.WriteLine(number);
> static void Print(ref int number) => Console.WriteLine(++number);
> 
> var number = 100;
> 
> Print(number);
100
> 
> Print(ref number);
101
> 
> Print(number);
101

위 예제 처럼 클래스 내에 동일한 이름의 메서드를 시그니처를 달리하여 여러 번 정의하는 것을 오버로드라고 합니다. 첫 번째 Print() 메서드는 값 형식의 매개 변수를 받아 사용하고 두 번째 Print() 메서드는 참조 형식의 매개 변수를 받아 사용하는데 구분 점은 ref 키워드를 사용했느냐 안 했느냐에 따라서 호출되는 메서드가 달라집니다. 사실, ref 키워드를 사용하는 메서드는 강의 이외에는 사용하지 않습니다.

메서드 오버라이드

그럼, 이번에는 메서드 오버라이드 예제를 하나 만들어보겠습니다.

코드: VirtualOverride.cs

> //[?] virtual ~ override: 화목한 가정의 표본 
> //[1] 부모 클래스 생성
> class Parent
. {
.     // virtual: 재정의해서 사용하도록 허용
.     public virtual void Work() => Console.WriteLine("프로그래머");
. }
> 
> //[2] 자식 클래스 생성
> class Child : Parent
. {
.     // override: 재정의해서 사용하겠다 지정
.     public override void Work() => Console.WriteLine("프로게이머");
. }
> 
> //[A] 부모 클래스의 인스턴스 생성
> (new Parent()).Work(); // 프로그래머
프로그래머
> 
> //[B] 자식 클래스의 인스턴스 생성
> (new Child()).Work(); // 프로게이머
프로게이머

Parent 클래스에서 이미 Work() 이름으로 선언된 메서드를 Child 클래스에서 동일한 이름의 Work() 메서드로 다시 정의해서 새로운 기능으로 사용하는 것을 메서드 오버라이드 또는 메서드 오버라이딩이라고 합니다. 부모 클래스인 ParentWork() 메서드를 호출하면 "프로그래머”가 출력되고 자식 클래스인 Child 클래스의 재정의된 Work() 메서드를 호출하면 “프로게이머”가 출력됩니다. 메서드 오버라이드를 구현할 때 가장 이상적인 코드 모양은 부모 클래스에 virtual이 붙은 메서드를 자식 클래스에서 orverride 키워드를 붙여 다시 정의해서 사용하겠다고 명확하게 지정하는 모양입니다.

가상(Virtual) 메서드

메서드 오버라이드는 다른 표현 방식으로 가상 메서드라고 합니다.

코드: VirtualMethod.cs

> //[1] 부모 클래스
> class Animal 
. {
.     //[?] virtual이 표시된 메서드: 가상 메서드
.     public virtual void Eat() => Console.WriteLine("Animal Eat");
. }
> 
> //[2] 자식 클래스
> class Cat : Animal
. {
.     public override void Eat() => Console.WriteLine("Cat Eat");
. }
> 
> //[A] 부모 인스턴스 생성
> Animal animal = new Animal();
> animal.Eat();
Animal Eat
> //[B] 자식 인스턴스 생성
> Animal cat = new Cat(); //[!] 부모 개체에 자식 인스턴스 담기
> cat.Eat();
Cat Eat

부모 클래스의 메서드에 virtual 키워드가 붙으면 자식 클래스에서는 해당 메서드를 그대로 물려받아 사용하거나 아니면 override 키워드를 붙여 새롭게 다시 만들어 사용할 수 있도록 규칙을 정할 수 있습니다.

메서드 오버라이드 봉인

메서드에도 sealed 키워드를 붙여 더 이상 오버라이드를해서 사용하지 못하도록 설정할 수 있습니다. 다음 소스 코드는 프로젝트 기반 소스인 VirtualOverrideBase.cs 이름으로 작성된 코드인데요. C# 인터렉티브에서 다음과 같이 실행해 볼 수 있습니다.

> class Parent
. {
.     public virtual void Work() => Console.WriteLine("프로그래머");
. }

위 코드와 같이 Work() 메서드를 virtual 키워드를 붙여 오버라이드 가능하도록 설정할 수 있습니다.

> class Child : Parent
. {
.     // sealed 키워드를 사용하여 멤버를 봉인(재정의 금지) 및 base 키워드로 부모 멤버 접근
.     public override sealed void Work() => base.Work();
. }

자식 클래스에서는 override 키워드로 부모의 Work() 메서드를 재정의하지만 sealed 키워드를 붙여 Child 클래스의 Work() 메서드는 더 이상 오버라이드가 불가능하게 설정할 수 있습니다. Work() 메서드 호출시 따로 메서드 본문을 구현하지 않고 base.Work()로 부모 클래스의 Work() 메서드를 실행했습니다.

> class GrandChild : Child
. {
.     public override void Work() => Console.WriteLine("프로게이머");
.     public void Play() => Console.WriteLine("프로게이머");
. }
(3,26): error CS0239: 'GrandChild.Work()': 상속된 'Child.Work()' 멤버는 봉인되어 있으므로 재정의할 수 없습니다.

만약, GrandChild와 같은 클래스에서 Child 클래스를 상속하고 sealed 키워드가 붙은 Work() 메서드를 다시 정의해서 사용하려고 시도하면 위 실행결과처럼 에러가 발생합니다.

ToString() 메서드 오버라이드

메서드 오버라이드 중에서 가장 많이 접해보는 기능은 아마도 ToString() 메서드 오버라이드일 것입니다. 클래스의 기본 메서드인 ToString() 메서드는 다음과 같은 특징을 가지고 있습니다.

  • Object 클래스에 정의되어 있는 ToString() 메서드는 기본적으로 클래스의 이름값을 반환시켜줍니다.
  • 정수형과 같이 대표가 되는 값이 들어있는 경우는 그 값을 문자열로 변환해서 출력해줍니다.
  • 내가 만든 클래스의 대표가 되는 속성 또는 값을 외부에 인스턴스명으로 사용해서 출력하고자할 때에는 ToString() 메서드를 재정의(Override)해서 사용해야 합니다.

ToString() 메서드 오버라이드(재정의)

Object 클래스에 구현되어 있는 ToString() 메서드는 모든 클래스에서 상속받아 사용되기에 이를 재정의해서 사용하는 예제를 만들어보겠습니다.

코드: ClassToString.cs

using System;

namespace ClassToString
{
    class Person
    {
        private string name;

        public Person(string name)
        {
            this.name = name;
        }

        // ToString() 메서드 오버라이드(재정의)
        public override string ToString() => $"[Person 클래스: {this.name}]";
    }

    class ClassToString
    {
        static void Main()
        {
            Person person = new Person("박용준");
            Console.WriteLine(person); // 개체를 문자열로 출력하면 ToString() 호출됨
        }
    }
}
[Person 클래스: 박용준]

클래스의 인스턴스를 문자열로 요청하면 ToString() 메서드가 실행됩니다. 기본으로는 클래스 이름이 문자열로 반환되지만 이를 재정의해서 원하는 문자열로 만들 수 있습니다.

[실습] 메서드 오버라이드(Override;재정의)를 통한 메서드 재사용

소개

부모 클래스에 이미 만들어져 있는 메서드를 자식 클래스에서 상속받아 사용할 때 부모 클래스의 메서드 이름을 다시 재사용하고자 할 때 메서드 오버라이드 기능을 사용합니다. 이 때 동일한 이름의 메서드에 접근할 때에는 자식 클래스의 멤버가 기준이됩니다. 이번 실습을 통해서 메서드 이름을 같을 때 부모 또는 자식 클래스의 멤버에 접근하는 방법을 살펴보겠습니다.

따라하기

(1) 새로운 C# 콘솔 프로젝트를 다음과 같이 생성합니다.

프로젝트 형식 템플릿 이름 위치
Visual C# 콘솔 응용 프로그램 MethodOverridePractice.cs C:\C#

(2) 솔루션 탐색기에서 Program.cs 파일을 MethodOverridePractice.cs 파일로 이름을 변경한 후 이미 만들어져 있는 모든 코드를 삭제한 후 다음과 같이 프로그램을 작성합니다.

메서드 오버라이드를 정리하는 차원에서 좀 더 긴 예제를 만들어보겠습니다.

코드: MethodOverridePractice.cs

using System;

namespace MethodOverridePractice
{
    public class ParentClass
    {
        public virtual void Hi1() => Console.WriteLine("기본: 안녕하세요.");
        public void Hi2() => Console.WriteLine("기본: 반갑습니다.");
        public void Hi3() => Console.WriteLine("기본: 또 만나요.");
    }

    public class ChildClass : ParentClass
    {
        public override void Hi1() => Console.WriteLine("파생: 안녕하세요.");
        public new void Hi2() => Console.WriteLine("파생: 반갑습니다.");
        public new void Hi3() => base.Hi3(); // 기본 클래스의 멤버에 접근
    }

    class Parent
    {
        public void Say() => Console.WriteLine("부모가 말하다.");
        public void Hi() => Console.WriteLine("부모가 인사하다.");
        public virtual void Walk() => Console.WriteLine("부모가 걷다.");
    }

    class Child : Parent
    {
        // 내가 새롭게 정의해서 사용하겠다.
        public void Say() => Console.WriteLine("자식이 말하다.");
        // 새롭게 정의 : 오버라이드
        public new void Hi() => Console.WriteLine("자식이 인사하다.");
        // 새롭게 정의 : 오버라이드(override):재정의
        public override void Walk() => Console.WriteLine("자식이 걷다.");
    }

    class MethodOverridePractice
    {
        static void Main()
        {
            ChildClass child = new ChildClass();
            child.Hi1(); // virtual -> override
            child.Hi2(); // X -> new
            child.Hi3(); // X <- base

            Child baby = new Child();
            baby.Say(); // 자식이 예의가 없다.
            baby.Hi(); // 자식이 예의바르다.
            baby.Walk(); // 부모가 관대하다.
        }
    }
}

(3) 소스 코드를 다 입력한 후 Ctrl+F5`를 눌러 프로그램을 실행하면 명령 프롬프트 창에 다음과 같이 출력됩니다.

파생: 안녕하세요.
파생: 반갑습니다.
기본: 또 만나요.
자식이 말하다.
자식이 인사하다.
자식이 걷다.

마무리

메서드를 재사용할 때 부모 클래스에 virtual 키워드를 붙이면 자식 클래스에는 override를 붙이고 부모 클래스에 아무런 키워드를 붙이지 않았을 때 자식 클래스에서 새롭게 해당 메서드를 재사용하고자 할 때에는 new 키워드를 붙이며 자식에서 부모 클래스의 멤버에 접근하고자 할 때에는 base 키워드를 사용합니다.

장 요약

오버라이드를 사용하면 부모 클래스에 정의된 기능을 자식 클래스에서 재정의해서 사용이 가능합니다. 부모 클래스는 기본적인 기능을 제공해주고, 자식 클래스에서는 기본 기능만 사용할건지 아니면 새롭게 기능을 확장해서 사용할건지를 결정하면 됩니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com