C# 다형성: 정적 바인딩 vs 동적 바인딩 완전 이해
다형성(Polymorphism)은 '여러 형태를 가짐'을 의미합니다. 객체 지향 프로그래밍에서 '하나의 인터페이스, 여러 기능'으로 표현되곤 합니다.
다형성은 정적(Static)과 동적(Dynamic)으로 나뉩니다.
- 정적 다형성: 함수에 대한 응답이 컴파일 타임에 결정 (조기 바인딩/Early Binding)
- 동적 다형성: 런타임에 결정 (지연 바인딩/Late Binding)
정적 다형성 (Static Polymorphism)
컴파일 타임에 함수와 객체 연결. C#은 두 가지 기법으로 구현합니다.
- 함수 오버로딩 (Function Overloading)
- 연산자 오버로딩 (Operator Overloading) - 다음 장에서 다룸
함수 오버로딩 (Function Overloading)
같은 범위에서 같은 이름의 함수를 여러 개 정의할 수 있습니다. 단, 매개변수의 타입이나 개수가 달라야 합니다. 반환 타입만 다른 것은 오버로딩이 아닙니다.
예제: 다양한 타입 출력
using System;
namespace PolymorphismApplication
{
class PrintData
{
public void Print(int i)
{
Console.WriteLine("정수 출력: {0}", i);
}
public void Print(double f)
{
Console.WriteLine("실수 출력: {0}", f);
}
public void Print(string s)
{
Console.WriteLine("문자열 출력: {0}", s);
}
}
class Tester
{
static void Main(string[] args)
{
PrintData p = new PrintData();
p.Print(5); // 정수 출력: 5
p.Print(500.263); // 실수 출력: 500.263
p.Print("Hello C#"); // 문자열 출력: Hello C#
Console.ReadKey();
}
}
}
동적 다형성 (Dynamic Polymorphism)
C#은 추상 클래스(Abstract Class)를 사용하여 인터페이스의 부분 구현을 제공합니다. 구현은 파생 클래스에서 완성됩니다. 추상 클래스는 추상 메서드를 포함하며, 이는 파생 클래스에서 구현됩니다.
추상 클래스 규칙
- 추상 클래스의 인스턴스 생성 불가
- 추상 클래스 외부에서 추상 메서드 선언 불가
sealed 클래스는 상속 불가, 추상 클래스는 sealed 불가
추상 클래스 예제
using System;
namespace PolymorphismApplication
{
abstract class Shape
{
public abstract int Area(); // 추상 메서드 (구현 없음)
}
class Rectangle : Shape
{
private int length, width;
public Rectangle(int a = 0, int b = 0)
{
length = a; width = b;
}
public override int Area() // 추상 메서드 구현 필수
{
Console.WriteLine("Rectangle 클래스 영역:");
return width * length;
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle r = new Rectangle(10, 7);
double a = r.Area();
Console.WriteLine("면적: {0}", a); // 70
Console.ReadKey();
}
}
}
가상 함수 (Virtual Functions)로 동적 다형성 구현
기본 클래스에서 정의하고 파생 클래스에서 다르게 구현하고 싶은 메서드에 virtual 키워드를 사용합니다. 호출할 메서드는 런타임에 결정됩니다.
using System;
namespace PolymorphismApplication
{
class Shape
{
protected int width, height;
public Shape(int a = 0, int b = 0) { width = a; height = b; }
public virtual int Area() // 가상 메서드
{
Console.WriteLine("부모 클래스 영역:");
return 0;
}
}
class Rectangle : Shape
{
public Rectangle(int a = 0, int b = 0) : base(a, b) { }
public override int Area() // 오버라이드
{
Console.WriteLine("Rectangle 클래스 영역:");
return width * height;
}
}
class Triangle : Shape
{
public Triangle(int a = 0, int b = 0) : base(a, b) { }
public override int Area()
{
Console.WriteLine("Triangle 클래스 영역:");
return width * height / 2;
}
}
class Caller
{
public void CallArea(Shape sh) // Shape 타입 매개변수
{
int a = sh.Area(); // 런타임에 실제 타입의 Area() 호출
Console.WriteLine("면적: {0}", a);
}
}
class Tester
{
static void Main(string[] args)
{
Caller c = new Caller();
Rectangle r = new Rectangle(10, 7);
Triangle t = new Triangle(10, 5);
c.CallArea(r); // Rectangle.Area() 호출
c.CallArea(t); // Triangle.Area() 호출
Console.ReadKey();
}
}
}
출력: Rectangle 클래스 영역: 면적: 70 / Triangle 클래스 영역: 면적: 25
정적 vs 동적 다형성 비교
| 구분 | 정적 다형성 | 동적 다형성 |
| 바인딩 시점 | 컴파일 타임 (조기 바인딩) | 런타임 (지연 바인딩) |
| 구현 기법 | 오버로딩, 연산자 오버로딩 | 가상 메서드, 추상 메서드 |
| 성능 | 빠름 (직접 호출) | 약간 느림 (vtable 조회) |
| 유연성 | 제한적 | 높음 (런타임 결정) |
| 용도 | 같은 작업의 타입별 처리 | 상속 계층에서의 동작 변경 |
핵심: 동적 다형성은 virtual/override 조합으로 구현하며, 기본 클래스 타입 변수로 파생 클래스 객체를 참조할 때 실제 객체 타입의 메서드가 호출됩니다. 이것이 OOP의 강력한 힘입니다.