한 클래스가 해당 클래스나 상위 클래스에서 하나 이상의 가상 메서드(virtual, abstract)를 가진다면 해당 클래스의 Method Table 메타데이터 내에 Virtual Table을 가지게 됩니다. Virtual Table은 기본적으로 메서드 포인터를 저장하는 배열입니다.
Virtual Table(이하 VTable)은 객체가 가리키는 Heap 메모리에 존재하는 Type Handle이 가리키는 곳의 Method Table 내에 위치합니다. CLR 버전에 따라 실제 VTable의 Offset위치는 약간 다르지만 Method Table 내에 위치하고 있습니다.
C# / .NET 에서 모든 클래스(또는 구조체)는 System.Object 클래스를 상속받으므로 Virtual Table을 가집니다. 모든 클래스는 System.Object 클래스의 3개의 가상 메서드(ToString(), Equals(), GetHashCode())를 자신의 VTable 안에 가지게 됩니다.
만약 Base 클래스가 가상 메서드를 가지지 않는다면 파생 클래스는 Base 클래스의 메서드를 VTable에 슬롯을 추가하지 않습니다.
파생 클래스가 조부모, 부모 등의 여러 계층관계를 가졌다면 최상위 클래스의 가상 메서드부터 파생 클래스까지 계층 순서대로 메서드 포인터 슬롯을 가지게 됩니다.
다음과 같은 예시로 Overriding을 살펴봅시다.
class A
{
public virtual void Run1()
{
Console.WriteLine("A.Run1");
}
public virtual void Run2() { }
}
class B : A
{
public override void Run1()
{
Console.WriteLine("B.Run1");
}
public void OtherRun() {}
}
class Program
{
static void Main(string[] args)
{
// 케이스-A
A a = new A();
a.Run1();
// 케이스-B
A x = new B();
x.Run1();
}
}
케이스-A의 경우는 클래스 A 타입이고 A 타입의 메서드 Run1()을 실행했으므로 "A.Run1"이 출력됩니다.
케이스-B의 경우는 클래스 B 객체를 생성하고 이를 A 타입의 변수에 할당했으므로 A의 Run1()을 실행한다고 생각할 수 있지만 클래스 B의 Run1()을 실행합니다.
객체지향 프로그래밍의 다형성을 보여준 예시라고 할 수 있습니다. 어떠한 원리로 다형성이 가능한 것일까요?
먼저 2가지 사실을 알고 가야 합니다.
1. 한 클래스로부터 객체가 생성되어 어떤 변수에 할당되었을 때, 변수의 타입에 상관없이 해당 클래스의 Method Table을 사용합니다.
2. 파생 클래스의 객체가 상위 클래스 변수에 할당되었을 때, 그 변수는 상위 클래스 범위 안에서 사용 가능한 메서드만을 사용할 수 있습니다.
다음은 Hiding에 대해서 알아봅시다. 파생 클래스에서 override를 사용하지 않고 동일한 메서드 명을 사용한 경우입니다.
여기서 new 키워드는 해당 메서드가 Base 클래스의 이름이 같은 메서드와 관련이 없다는 것을 의미합니다. new 키워드를 쓰지 않으면 Warning이 뜨지만 실제 런타임에서는 new를 사용했을 때와 동일하게 실행됩니다.
class A
{
public virtual void Run1()
{
Console.WriteLine("A.Run1");
}
public virtual void Run2()
{
Console.WriteLine("A.Run2");
}
}
class B : A
{
public override void Run1()
{
Console.WriteLine("B.Run1");
}
public new void Run2()
{
Console.WriteLine("B.Run2");
}
}
class Program
{
static void Main(string[] args)
{
//케이스-A
A a = new B();
a.Run2(); // 베이스의 Run2 실행
//케이스-B
B b = (B) a;
b.Run2(); // 파생클래스 Run2 실행
}
}
Hiding의 경우 객체지향 프로그래밍의 다형성을 활용하지 않습니다. 따라서, 특수한 경우가 아니면 사용되지 않는다고 합니다.