CodeGym /행동 /C# SELF /C#의 가상 메서드: 다형성과 오버라이딩

C#의 가상 메서드: 다형성과 오버라이딩

C# SELF
레벨 20, 레슨 2
사용 가능

1. 소개

상상해봐, Animal이라는 기본 클래스에 Speak() 메서드가 있어. 모든 동물은 기본적으로 "어떤 소리"라고 출력하지. 근데 Dog랑 Cat 같은 자식 클래스를 만들어서, 얘네가 "어떤 소리"가 아니라 진짜 자기 소리("멍멍!"이랑 "야옹!")를 내게 하고 싶어.

각 자식 클래스마다 Speak()를 따로 쓸 수도 있겠지. 근데 Animal[] 타입의 컬렉션으로 작업하면, animal.Speak()를 호출해도 결국 Animal의 메서드만 불러와. 왜냐면 C#은 기본적으로 변수 타입을 보고 판단하지, 실제 객체 타입을 보지 않거든.

이해하기 쉬운 예시:


Animal dog = new Dog();
dog.Speak(); // 어라, 뭐가 출력될까?

기본적으로는 "어떤 소리"가 나와. Dog에 Speak()가 따로 있어도 말이지. 그럼 어떻게 해야 할까? 기본 클래스에서 virtual로, 자식 클래스에서 override로 메서드를 선언해야 해.

가상 메서드란?

virtual 메서드는 기본 클래스에서 virtual로 선언되고, 자식 클래스에서 override로 재정의할 수 있도록 만들어진 메서드야.

이런 메서드를 기본 클래스 참조로 호출하면, 실제 "진짜" 객체의 메서드가 호출돼. 현실에서도 그렇잖아: 동물을 길들였는데 그게 고양이면, "야옹!"이라고 하지 "어떤 소리"라고 하진 않잖아.

가상 메서드는 C#에서 다형성의 핵심이야.

2. 가상 메서드 선언하기

가상 메서드 선언하는 방법

기본 클래스에서 virtual 키워드로 메서드를 선언해:


public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("어떤 소리");
    }
}
기본 클래스에서 가상 메서드 선언하기

자식 클래스에서는 override 키워드를 써:


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

public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("야옹!");
    }
}
자식 클래스에서 가상 메서드 오버라이딩하기

예시: 다형성 시연


// 예를 들어, Program.cs 파일에서

// 가상 메서드 Speak가 있는 기본 클래스 Animal
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("어떤 소리");
    }
}

// Dog 클래스, Speak 동작 오버라이딩
public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("멍멍!");
    }
}

// Cat 클래스, 역시 Speak 오버라이딩
public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("야옹!");
    }
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };

        foreach (Animal animal in animals)
        {
            animal.Speak(); // 출력: "어떤 소리", "멍멍!", "야옹!"
        }
    }
}

봤지? 가상 메서드가 얼마나 마법 같은지! 변수 타입이 Animal이어도, 실제 구현체의 메서드가 호출돼.

3. "속사정": 가상 테이블

메커니즘 간단 설명

메서드를 가상으로 선언하면, 컴파일러가 각 타입마다 특별한 "가상 테이블"에 그 메서드를 추가해(마치 식당 메뉴판처럼). 이런 메서드를 호출할 때 C#은 변수 타입이 아니라 실제 참조된 객체를 보고, 진짜 객체 타입에 맞는 메서드를 "끼워넣어" 호출해.

클래스에 자체 레시피가 있으면, C#이 알아서 범용 레시피 대신 그걸 써주는 거지.

비유: "작은 글씨로 항목 추가 가능한 계약서"

임대 계약서(기본 클래스)가 있다고 해봐. 거기엔 임차인이 기본적으로 매달 고정 금액을 낸다고 써있지(PayRent() 메서드). 근데 너는 약삭빠른 집주인이라서, 계약서 부록에 특별 조건을 쓸 수 있게 해(가상 메서드). 임차인(자식 클래스)이 이 권리를 쓰면, override로 자기만의 조건을 적고, 이제 임대료가 특별하게 계산되는 거야.

4. 언제 가상 메서드를 써야 할까?

  • 기본 클래스의 메서드가 대부분 자식 클래스에서 그대로 쓰이지만, 일부는 동작을 바꿔야 할 때.
  • 클래스 계층 구조를 만들면서, 일부 로직을 확장하거나 바꿀 일이 있을 때.
  • 비즈니스 로직처럼 다양한 연산이 각자 다른 동작을 해야 하는 유연한 아키텍처를 만들 때.

실제 프로젝트에서 가상 메서드는 템플릿 메서드(Template Method), 전략(Strategy) 패턴 등, OOP의 절반을 차지하는 패턴의 핵심이야.

일반 메서드와 비교

일반 메서드 가상 메서드
오버라이딩 가능? 아니 응 (override)
호출 방식 변수 타입 기준 "진짜" 객체 타입 기준
다형성 아니 응

자주 묻는 질문

  • 생성자를 가상으로 만들 수 있어?
    절대 안 돼! 생성자는 절대 가상일 수 없어. 항상 객체 타입에 따라 동작해.
  • 가상 메서드가 static일 수 있어?
    아니, 인스턴스 메서드만 가상일 수 있어. static 메서드는 다형성에 안 들어가. 객체가 없으니까 누가 오버라이딩해?
  • 필드도 가상일 수 있어?
    아니, 메서드, 프로퍼티, 이벤트만 가능해.

5. 실습: "동물의 세계" 앱 확장하기

우리 연습용 앱에서 이미 간단한 동물 계층 구조를 만들었지. 이제 동물마다 다르게 먹을 수 있도록 새로운 가상 메서드를 추가해보자!

주석 달린 코드 예시


// 기본 클래스에 가상 메서드 Eat 추가
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("어떤 소리");
    }

    public virtual void Eat()
    {
        Console.WriteLine("일반 사료를 먹는다.");
    }
}

// Dog에서 Eat 오버라이딩
public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("멍멍!");
    public override void Eat() => Console.WriteLine("강아지 사료를 먹는다.");
}

// Cat도 자기만의 동작
public class Cat : Animal
{
    public override void Speak() => Console.WriteLine("야옹!");
    public override void Eat() => Console.WriteLine("생선을 먹는다.");
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };
        foreach (var animal in animals)
        {
            animal.Speak();
            animal.Eat();
            Console.WriteLine("---");
        }
    }
}

이제 고양이랑 강아지 각각 자기 방식대로 먹어! 기본 Animal은 여전히 뭔가 이상한 걸 먹지만, 그게 "디폴트" 기능이지.

6. 흔한 실수와 주의점

  • 자식 클래스에서 override를 깜빡하면, 기본 클래스의 메서드가 그대로 남아버려.
  • 반대로, 기본 클래스에서 virtual을 빼먹으면, 자식에서 override를 쓸 수 없어.

virtual 없이 override는 불가

C# 컴파일러는 기본 클래스에서 가상(virtual)이나 추상(abstract)으로 선언되지 않은 메서드는 오버라이딩 못 하게 막아.

override가 왜 필요할까?

override는 컴파일러랑 코드 읽는 사람 모두에게 "나 그냥 새 메서드 쓰는 게 아니라, 기본 클래스의 걸 일부러 바꾸는 거야"라고 명확하게 알려줘. 실수로 시그니처만 같은 새 메서드를 만들지 않게 해주지.

override를 빼먹고 새 메서드만 썼을 때

public class Dog : Animal
{
    public void Speak()
    {
        Console.WriteLine("멍멍!");
    }
}
이건 오버라이딩 아님! 다형성 안 됨.

이건 오버라이딩이 아니야! Animal 타입 변수로 Dog를 참조하면, 동물 메서드("어떤 소리")만 호출돼. 타입의 함정이 이렇게 무섭다니까!

2
과제
C# SELF, 레벨 20, 레슨 2
잠금
가상 메서드를 가진 클래스 만들기
가상 메서드를 가진 클래스 만들기
2
과제
C# SELF, 레벨 20, 레슨 2
잠금
추상 메서드와 추가 로직
추상 메서드와 추가 로직
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION