다형성 이해하기

Posted by Eun JongHyeok on December 18, 2022
  1. 다형성이란
  2. C#에서의 다형성
    1. 키워드
    2. Virtual Table
    3. 상속 예제
  3. 참고

다형성이란

객체지향에서 다형성이란 하나의 객체가 다양한 형(Type)을 가질 수 있다는 것을 의미합니다. 주로 파생 클래스의 객체는 베이스 클래스로 형변환하여 사용하는 경우가 많습니다.

C#에서의 다형성

앞서 포스팅한 프로퍼티에서 다형성을 설명을 못드렸는데 메서드와 동일한 방식으로 다형성이 구현하고 있어 메서드 기준으로 설명드리도록 하겠습니다.

키워드

  • virtual
  • override
  • new

C#에서 다형성과 관련된 키워드들입니다.

virtual의 경우 베이스 클래스의 메서드 앞에 붙을 수 있는데요. virtual이 붙어야 파생클래스에서 오버라이딩 기능을 사용 할 수 있습니다.

override의 경우 파생 클래스의 메서드에 붙일 수 있는데 베이스 클래스의 가상 메서드를 덮어쓰게 해줍니다.

new 키워드의 경우 베이스 클래스의 메서드와 독립적으로 정의를 할 수 있게하고 베이스 클래스의 메서드를 숨김 처리 해줍니다.

Virtual Table

다형성과 관련한 .Net 문서에서는 가상 테이블에 대한 설명이 없지만 다형성과 관련된 설명을 하기에는 가상 테이블을 빼놓고 설명하면 이해하기가 어렵기 때문에 먼저 설명을 하도록 하겠습니다.

가상 테이블은 말 그대로 가상 메서드를 가리키는 테이블입니다.

객체를 생성할 때 해당 클래스에 가상 메서드가 있으면 객체에는 가상테이블을 가르키는 포인터를 가지게 됩니다.(C++ 기준)

C#의 경우 모든 클래스가 Object 클래스의 파생 클래스이므로 모든 클래스가 가상 테이블을 가르키는 포인터를 갖습니다.

즉, 가상 메서드는 따로 관리하는 가상 테이블이 있고 객체는 그 가상테이블을 가리키는 포인터를 가지고 있다는 것을 이해하셔야합니다. 가상테이블을 가리키는 포인터는 객체를 생성할 때 그 객체의 타입에 맞는 가상 테이블을 가리키고 있습니다.

상속 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
namespace PolymorphismTest
{
    class Program
    {
        static void Main(string[] args)
        {
            BaseClass bc = new BaseClass();
            DerivedClass dc = new DerivedClass();
            BaseClass bcdc = new DerivedClass();

            Console.WriteLine("BaseClass bc = new BaseClass()");
            // The following two calls do what you would expect. They call  
            // the methods that are defined in BaseClass.  
            bc.Method1();
            bc.Method2();
            bc.Method3();
            bc.Method4();
            // Output:  
            // Base - Method1  
            // Base - Method2
            // Base - Method3
            // Base - Method4

            Console.WriteLine();

            Console.WriteLine("DerivedClass dc = new DerivedClass()");
            // The following two calls do what you would expect. They call  
            // the methods that are defined in DerivedClass.  
            dc.Method1();
            dc.Method2();
            dc.Method3();
            dc.Method4();
            // Output:  
            // Derived - Method1  
            // Derived - Method2
            // Derived - Method3
            // Base - Method4

            Console.WriteLine();

            // The following two calls produce different results, depending
            // on whether override (Method1) or new (Method2) is used.
            Console.WriteLine("BaseClass bcdc = new DerivedClass();");
            bcdc.Method1();
            bcdc.Method2();
            bcdc.Method3();
            bcdc.Method4();
            // Output:  
            // Derived - Method1  
            // Base - Method2
            // Base - Method3
            // Base - Method4
        }
    }

    class BaseClass
    {
        public virtual void Method1()
        {
            Console.WriteLine("Base - Method1");
        }

        public virtual void Method2()
        {
            Console.WriteLine("Base - Method2");
        }

        public void Method3()
        {
            Console.WriteLine("Base - Method3");
        }

        public void Method4()
        {
            Console.WriteLine("Base - Method4");
        }
    }

    class DerivedClass : BaseClass
    {
        public override void Method1()
        {
            Console.WriteLine("Derived - Method1");
        }

        public new void Method2()
        {
            Console.WriteLine("Derived - Method2");
        }

        // base 클래스에서 virtual 메서드가 아닌 메서드는 override를 할 수 없다. 
        public new void Method3()
        {
            Console.WriteLine("Derived - Method3");
        }
    }
}

.Net 문서의 예제에서 조금 수정하여 클래스가 어떻게 구성되어 있는지와 다형성이 어떻게 구현되는지 확인해보겠습니다.

우선 virtual이나 override가 붙지 않은 메서드는 가상 메서드가 아닙니다.

BaseClass의 메서드 정보들입니다.

BaseClass.PNG

Method1과 Method2만 가상 메서드이고 BaseClass의 가상메서드 테이블에 저장됩니다.

Method3과 Method4의 경우 BaseClass 객체 내부에 포함됩니다.

DerivedClass의 메서드 정보들입니다.

DerivedClass.PNG

DerivedClass의 가상 메서드는 Method1과 BaseClass의 Method2입니다. 가상 메서드 테이블에는 BaseClass의 Method1은 DerivedClass의 Method1로 override 되어버렸고 BaseClass의 Method2가 포함됩니다. 나머지는 DerivedClass 객체에 포함됩니다.(Object는 생략)

1
2
3
4
5
6
7
8
9
10
11
12
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();

dc.Method1();
dc.Method2();
dc.Method3();
dc.Method4();

bcdc.Method1();
bcdc.Method2();
bcdc.Method3();
bcdc.Method4();

실제 출력되는 부분을 살펴봅시다.

DerivedClass dc = new DerivedClass();의 경우 DerivedClass를 생성하고 객체의 타입 역시 DerivedClass입니다.

위의 DerivedClass 메서드 정보들을 보면 Method2,3이 중복되는 것을 확인할 수 있습니다. 하지만 new 키워드가 있어 BaseClass의 Method2,3을 숨김 처리하여 중복을 막아줍니다.

다형성이 적용되는 BaseClass bcdc = new DerivedClass();의 경우 DerivedClass를 생성하고 객체의 타입 BaseClass가 됩니다.

객체를 DerivedClass로 생성했기 때문에 DerivedClass의 메서드 구조를 가지고 있습니다. 하지만 객체의 타입이 BaseClass이기 때문에 DerivedClass의 Method2,3에는 접근을 하지 못합니다.

앞서 가상 테이블을 설명드릴 때 가상 메서드는 가상 메서드 테이블로 따로 관리된다 말씀 드렸습니다. bcdc의 가상 메서드 테이블은 DerivedClass의 가상 메서드 테이블입니다.

DerivedClass의 가상 메서드 테이블에는 DerivedClass.Method1과 BaseClass.Method2가 있습니다. BaseClass.Method1은 DerivedClass.Method1에 덮어쓰기를 당했기 때문입니다.

그렇기에 bcdc.Method1();은 가상메서드 테이블을 통해 DerivedClass.Method1이 호출되고 bcdc.Method2();의 경우 DerivedClass.Method2는 접근불가이며, 가상 메서드인 BaseClass.Method2가 호출되는 것입니다. 또한 bcdc.Method3();의 경우 DerivedClass.Method3는 접근불가이며, BaseClass 객체에 있던 BaseClass.Method2가 호출되는 것입니다.

참고

다형성 문서
.Net 문서에서는 다형성을 이용한 버전관리, ToString 메서드 재정의와 관련된 예제들도 확인할 수 있으니 참고하면 좋을 것 같습니다.

참고한 다른 블로그
virtual table에 대해서는 해당 블로그가 그림으로 표현을 잘되어 있어 이해하는데 도움을 줄 것 같습니다.


polymorphism
virtual
override
new
vtable

← Previous Post