C#은 객체 지향 프로그래밍 언어로, 다형성(Polymorphism)을 지원합니다. 다형성의 중요한 두가지 개념이 바로 오버로딩(Overloading)과 오버라이딩(Overriding)인데요. 이 글에서는 오버로딩과 오버라이딩이 무엇인지, 그 특징과 사용 이유, 그리고 실제 예제와 함께 사용하는 방법에 대해 알아보겠습니다.
1. 오버로딩(Overloading) 이란?
오버로딩(Overloading)은 같은 이름의 메서드를 여러 개 정의하는 기법으로, 메서드의 매개변수의 수나 타입을 다르게 설정하여 동일한 메서드 이름을 여러 용도로 사용할 수 있게 해줍니다. C#에서는 메서드 오버로딩뿌만 아니라 생성자 오버로딩도 지원합니다.
오버로딩을 통해 함수 이름을 재사용할 수 있어 코드의 가독성과 유지보수성을 높일 수 있습니다.
예제:
public class Calculator
{
// 두 정수의 합을 구하는 메서드
public int Add(int a, int b)
{
return a + b;
}
// 세 정수의 합을 구하는 메서드 (오버로딩)
public int Add(int a, int b, int c)
{
return a + b + c;
}
// 두 실수의 합을 구하는 메서드 (오버로딩)
public double Add(double a, double b)
{
return a + b;
}
}
위 예제에선 'Add'메서드는 같은 이름을 가지지만, 매개변수의 타입과 수가 다르게 정의되어있습니다. 이를 통해 'Add' 메서드는 다양한 유형의 덧셈 연산을 수행할 수 있습니다.
2. 오버로딩의 특징
오버로딩은 C#에서 다형성을 구현하는 중요한 방법 중 하나입니다. 다음은 오버로딩의 주요 특징입니다.
1) 같은 이름, 다른 시그니처
오버로딩은 동일한 이름을 가진 여러 메서드를 정의할 수 있지만, 각 메서드는 다른 매개변수 시그니처를 가져야 합니다. 시그니처는 메서드의 매개변수 타입, 수, 순서 등을 의미합니다.
public void Print(int number) { }
public void Print(string text) { }
public void Print(int number, string text) { }
위의 세 메서드는 모두 'Print'라는 동일한 이름을 가지고 있지만, 각각의 시그니처가 다르기 때문에 오버로딩이 가능합니다.
2) 반환 타입은 오버로딩과 무관
오버로딩은 오직 메서드의 시그니처에만 영향을 받습니다. 즉, 반환 타입이 다르다고 해서 메서드를 오버로딩할 수 있는 것은 아닙니다.
public int Calculate(int a) { return a * 2; }
public double Calculate(int a) { return a * 2.0; } // 오류: 매개변수 시그니처가 동일하므로 오버로딩 불가
3) 컴파일 타임 다형성
오버로딩은 컴파일 타임 다형성(Compile-time Polymorphism)을 제공하여, 컴파일러는 메서드 호출 시점에 매개변수 타입과 수를 기반으로 적절한 메서드를 선택합니다.
3. 오버로딩을 사용하는 이유
1) 코드 가독성 향상
같은 의미의 작업을 수행하는 메서드들이 동일한 이름을 가지도록 함으로써, 코드의 가독성을 높일 수 있습니다. 예를 들어, 여러 타입의 덧셈 작업을 각각 다른 이름의 메서드로 정의하는 대신, 모두 'Add'라는 이름으로 정의하면 코드가 더 명확해 질 수 있습니다.
2) 유지보수성 향상
같은 이름의 메서드를 다양한 시그니처로 정의할 수 있으므로, 메서드의 이름을 기억하기 쉽고 코드의 유지보수가 용이해집니다. 새로운 요구사항이 생길 때, 기존 메서드에 새로운 오버로드를 추가하여 코드를 확장할 수 있습니다.
3)매개변수에 따른 다양한 동작
오버로딩을 통해 동일한 메서드 이름으로 다양한 매개변수 조합에 따라 다른 동작을 수행할 수 있고, 이를 통해 코드의 유연성을 높일 수 있습니다.
4. 오버로딩 사용 방법
1) 기본 메서드 오버로딩
가장 일반적인 오버로딩은 매개변수의 타입이나 수를 변경하여 동일한 이름의 메서드를 여러 개 정의하는 것 입니다.
public class Printer
{
public void Print(string text)
{
Console.WriteLine(text);
}
public void Print(int number)
{
Console.WriteLine(number);
}
public void Print(string text, int number)
{
Console.WriteLine($"{text}: {number}");
}
}
위 코드에서 'Print'메서드는 문자열, 정수, 문자열과 정수의 조합을 출력하는 세 가지 오버로드를 제공합니다.
2) 생성자 오버로딩
오버로딩은 메서드뿐만 아니라 생성자(Constructor)에도 적용할 수 있습니다. 생성자 오버로딩을 통해 객체를 다양한 방법으로 초기화할 수 있습니다.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 기본 생성자
public Person()
{
Name = "Unknown";
Age = 0;
}
// 이름만 초기화하는 생성자
public Person(string name)
{
Name = name;
Age = 0;
}
// 이름과 나이를 초기화하는 생성자
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
해당 코드에서 'Person' 클래스는 세 가지 생성자를 가지고 있습니다. 이를 통해 객체를 다양한 방식으로 초기화할 수 있습니다.
3) 연산자 오버로딩
C#에서도 연산자도 오버로딩할 수 있습니다. 연산자 오버로딩을 통해 사용자 정의 타입에 대해 기본 연산자를 정의할 수 있습니다.
public class ComplexNumber
{
public int Real { get; set; }
public int Imaginary { get; set; }
public ComplexNumber(int real, int imaginary)
{
Real = real;
Imaginary = imaginary;
}
// '+' 연산자 오버로딩
public static ComplexNumber operator +(ComplexNumber c1, ComplexNumber c2)
{
return new ComplexNumber(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
}
}
위 코드에서 '+' 연산자가 'ComplexNumber' 타입에 대해 오버로딩되어, 두 복소수를 더하는 기능을 제공합니다.
5. 오버라이딩(Overriding)이란?
오버라이딩(Overriding)은 부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의하여 사용하는 기법입니다. 오버라이딩은 런타임 다형성(Runtime Polymorphism)을 구현하는 중요한 방법 중 하나로, 자식 클래스에서 부모 클래스의 메서드를 변경하여 자식 클래스만의 방식으로 동작하게 할 수 있습니다.
오버라이딩을 사용하려면 부모 클래스에서 해당 메서드가 'virtual' 키워드로 정의되어 있어야 하며, 자식 클래스에서는 'override' 키워드를 사용해 재정의할 수 있습니다.
예제:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal sound");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Bark");
}
}
위 예제에서 'Animal' 클래스의 'Speak' 메서드는 'Dog'클래스에서 오버라이딩되어, 개가 짖는 소리("Bark")를 출력하도록 재정의되었습니다.
6. 오버라이딩의 특징
1)동일한 시그니처
오버라이딩된 메서드는 부모 클래스에서 정의된 메서드와 동일한 시그니처(이름, 매개변수 타입, 수, 순서)를 가져야합니다. 시그니처가 다를 경우, 이는 오버로딩이 되지 오버라이딩이 되지는 않습니다.
public class Animal
{
public virtual void Speak() { }
}
public class Dog : Animal
{
public override void Speak() { } // 정상: 시그니처 동일
// public override void Speak(int volume) { } // 오류: 매개변수가 다르면 오버라이딩 불가
}
2) 'virtual', 'override', 'sealed' 키워드
- virtual: 부모 클래스에서 메서드를 오버라이딩할 수 있도록 정의할 때 사용합니다.
- override: 자식 클래스에서 부모 클래스의 메서드를 재정의할 때 사용합니다.
- sealed: 자식 클래스에서 오버라이딩된 메서드를 더 이상 다른 클래스에서 오버라이딩하지 못하도록 막을 때 사용합니다.
public class Animal
{
public virtual void Speak() { }
}
public class Dog : Animal
{
public sealed override void Speak() { } // 더 이상 오버라이딩 불가
}
3) 런타임 다형성
오버라이딩은 런타임 다형성을 제공합니다. 이는 프로그램 실행 중에 어떤 메서드가 호출될지 결정되며, 부모 클래스의 참조 변수가 자식 클래스의 인스턴스를 참조할 때 발생합니다.
Animal myDog = new Dog();
myDog.Speak(); // 출력: Bark
위 코드에서 'myDog'은 'Animal' 타입이지만, 실제로는 'Dog'클래스의 인스턴스를 참조하고 있습니다. 따라서 'Speak' 메서드는 'Dog' 클래스에서 재정의된 메서드가 호출됩니다.
7. 오버라이딩을 사용하는 이유
1) 다형성 구현
오버라이딩은 객체 지향 프로그래밍의 핵심 원칙 중 하나인 다형성을 구현하는 방법입니다. 이를 통해 상위 클래스의 참조 변수가 하위 클래스의 인스턴스를 가리킬 때, 실제로 호출되는 메서드는 하위 메서드에서 정의된 메서드가 됩니다.
2) 상속 관계에서 기능 확장
부모 클래스에서 제공하는 기본 동작을 기반으로, 자식 클래스에서 그 동작을 확장하거나 변경할 수 있습니다. 이를 통해 클래스 계층 구조에서 유연성을 높일 수 있습니다.
3) 코드 재사용
오버라이딩을 통해 부모 클래스의 코드를 재사용하면서, 필요한 부분만 변경하여 새로운 기능을 추가할 수 있습니다. 이는 코드의 중복을 줄이고 유지보수를 쉽게 만듭니다.
8. 오버라이딩 사용 방법
오버라이딩을 사용하는 방법은 간단합니다. 부모 클래스에서 virtual로 정의된 메서드를 자식 클래스에서 override키워드를 사용해 재정의하면 됩니다. 몇 가지 예제를 통해 오버라이딩을 사용하는 방법을 살펴보겠습니다.
1) 기본 메서드 오버라이딩
부모 클래스에서 정의된 메서드를 자식 클래스에서 오버라이딩하여 새로운 동작을 구현할 수 있습니다.
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal sound");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Bark");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Meow");
}
}
위 코드에서 Dog와 Cat 클래스는 Animal 클래스의 Speak 메서드를 오버라이딩하여 각각 다른 소리를 출력합니다.
2) 기본 동작 유지 및 확장
오버라이딩 메서드에서 부모 클래스의 기본 동작을 유지하면서 추가 기능을 구현할 수도 있습니다.
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal sound");
}
}
public class Dog : Animal
{
public override void Speak()
{
base.Speak(); // 부모 클래스의 기본 동작 호출
Console.WriteLine("Bark");
}
}
위 코드에서 Dog 클래스의 Speak 메서드는 부모 클래스의 Speak 메서드를 호출한 후, 추가로 "Bark"를 출력합니다.
3) sealed 키워드로 오버라이딩 막기
자식 클래스에서 오버라이딩된 메서드가 더 이상 다른 클래스에서 오버라이딩되지 않도록 sealed키워드를 사용할 수 있습니다.
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal sound");
}
}
public class Dog : Animal
{
public sealed override void Speak()
{
Console.WriteLine("Bark");
}
}
public class Bulldog : Dog
{
// 오류: Dog 클래스의 Speak 메서드는 sealed로 인해 오버라이딩 불가
// public override void Speak()
// {
// Console.WriteLine("Bulldog bark");
// }
}
위 코드에서 Dog 클래스의 Speak 메서드는 sealed로 지정되어 있어, Bulldog 클래스에서 오버라이딩할 수 없습니다.
정리
오버로딩과 오버라이딩은 C#에서 다형성을 구현하는 두 가지 중요한 방법입니다. 오버로딩은 같은 이름의 메서드를 매개변수 시그니처를 다르게 하여 여러 개 정의하는 것이며, 주로 컴파일 타임 다형성을 구현합니다. 반면에, 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 재정의하여 런타임 다형성을 구현하는 방법입니다.
오버로딩을 통해 코드의 가독성과 유연성을 높일 수 있으며, 오버라이딩을 통해 상속된 클래스의 동작을 변경하거나 확장할 수도 있습니다. 이 두 가지 기법을 잘 활용하면, 더 효율적이고 유연한 객체 지향 프로그래밍을 할 수 있습니다.
지금까지 C# 오버로딩과 오버라이딩에 대해 알아보았습니다.
현재 글의 내용 중 틀린 부분이나, 지적하실 내용이 있으시다면 언제든지 알려주세요! 읽어주셔서 감사합니다.
'CS 공부' 카테고리의 다른 글
[Unity] 생명주기(Life Cycle) (0) | 2024.09.04 |
---|---|
[C#] 참조 매개변수 전달 (ref, out) (0) | 2024.08.31 |
[C#] 람다식(Lambda Expression) (0) | 2024.08.16 |
[C#] 변수 종류와 키워드 (0) | 2024.08.09 |
[C#] 클래스와 구조체 (Class & Structure) (0) | 2024.08.04 |