다형성(Polymorphism)은 정적(static)과 동적(dynamic) 두 가지 방식으로 구현할 수 있습니다. 정적 다형성에서는 함수 호출에 대한 응답이 컴파일 시점에 결정되고, 동적 다형성에서는 런타임 시점에 결정됩니다. 이 두 가지 개념을 제대로 이해하면 C# 코드를 더 유연하고 효율적으로 설계할 수 있습니다.
컴파일 타임 다형성 (정적 바인딩)
컴파일 시점에 함수와 객체를 연결하는 메커니즘을 초기 바인딩(Early Binding)이라고 하며, 정적 바인딩(Static Binding) 또는 조기 바인딩이라고도 부릅니다. 대표적인 예로는 메서드 오버로딩(Method Overloading)과 연산자 오버로딩이 있으며, 컴파일러가 어떤 메서드가 호출될지 미리 판단하므로 실행 속도가 빠르다는 장점이 있습니다.
public class Calculator
{
// 메서드 오버로딩 - 컴파일 시점에 호출 대상이 결정됨
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
런타임 다형성 (동적 바인딩)
런타임 다형성은 메서드 오버라이딩(Method Overriding)을 기반으로 작동하며, 동적 바인딩(Dynamic Binding) 또는 후기 바인딩(Late Binding)이라고도 알려져 있습니다. 실제 객체의 타입에 따라 호출될 메서드가 런타임에 결정되므로, 코드의 확장성과 유연성이 크게 향상됩니다.
public class Animal
{
public virtual void Speak() => Console.WriteLine("동물이 소리를 냅니다.");
}
public class Dog : Animal
{
public override void Speak() => Console.WriteLine("멍멍!");
}
Animal animal = new Dog();
animal.Speak(); // 런타임에 Dog.Speak()가 호출됨 - 출력: 멍멍!
추상 클래스와 가상 함수의 역할
추상 클래스(Abstract Class)는 구현체가 없는 추상 메서드를 포함하며, 이 메서드들은 파생 클래스에서 반드시 구현해야 합니다. 파생 클래스는 상속받은 기능보다 더 구체화된(specialized) 동작을 제공합니다. 즉, 동적 다형성은 추상 클래스와 가상 함수(virtual function)를 통해 구현됩니다.
정적 바인딩 vs 동적 바인딩 비교
| 구분 | 정적 바인딩 | 동적 바인딩 |
|---|---|---|
| 바인딩 시점 | 컴파일 시점 | 런타임 시점 |
| 핵심 기능 | 메서드 오버로딩 | 메서드 오버라이딩 |
| 관련 키워드 | - | virtual, override, abstract |
| 장점 | 빠른 실행 속도 | 유연성과 확장성 |
두 바인딩 방식은 서로 대립하는 개념이 아니라 상황에 따라 함께 사용됩니다. 성능이 중요한 단순한 로직에는 정적 바인딩을, 객체 지향 설계의 확장성이 필요한 경우에는 동적 바인딩을 활용하는 것이 좋습니다.