다형성은 메서드 재정의 및 메서드 오버로딩을 포함하는 OOPS 원칙 중 하나입니다. Virtual 및 Override 키워드는 메서드 재정의에 사용되고 new 키워드는 메서드 숨김에 사용됩니다. 이 포스팅에서는 C# 코드를 사용하여 각 키워드에 대해 자세히 설명합니다.
단순한 클래스 상속
클래스 A, B, C가 있는 상속 관계를 생각해 봅시다. A는 상위/기본 클래스이고 B는 클래스 A에서 파생되며, C는 클래스 B에서 파생됩니다.
Class A > Class B > Class C
Test() 메서드가 기본 클래스 A에 선언되어 있습니다. 클래스 B와 C는 메서드가 없습니다.
using System;
namespace Polymorphism {
class A {
public void Test() { Console.WriteLine("A::Test()"); }
}
class B : A {}
class C : B {}
class Program {
static void Main(string[] args) {
A a = new A();
a.Test(); // output --> "A::Test()"
B b = new B();
b.Test(); // output --> "A::Test()"
C c = new C();
c.Test(); // output --> "A::Test()"
Console.ReadKey();
}
}
}
이번에는 한번 아래와 같이 모든 클래스 A, B, C에 Test() 메서드가 있다고 가정합니다.
using System;
namespace Polymorphism
{
class A
{
public void Test() { Console.WriteLine("A::Test()"); }
}
class B : A
{
public void Test() { Console.WriteLine("B::Test()"); }
}
class C : B
{
public void Test() { Console.WriteLine("C::Test()"); }
}
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new B();
C c = new C();
a.Test(); // output --> "A::Test()"
b.Test(); // output --> "B::Test()"
c.Test(); // output --> "C::Test()"
a = new B();
a.Test(); // output --> "A::Test()"
b = new C();
b.Test(); // output --> "B::Test()"
Console.ReadKey();
}
}
}
위의 프로그램을 실행하면 성공적으로 실행됩니다. 그러나 이 프로그램은 아래와 같이 두 가지 경고를 표시합니다.
'Polymorphism.B.Test()' hides inherited member 'Polymorphism.A.Test()'. Use the new keyword if hiding was intended.
'Polymorphism.C.Test()' hides inherited member 'Polymorphism.B.Test()'. Use the new keyword if hiding was intended.
메소드 은닉 : new 키워드에 관하여
위의 예에서 에러가 왜 생겼다고 생각하시나요? C#은 '메서드 숨김'을 지원하기 때문에 경고를 생성합니다. 여러분은 위에서 묵시적으로 메서드 숨김을 했지만, 명시적으로 하라는 에러가 뜬 것입니다.
파생 클래스(Derived Class, Sub Class, Child class)에서 기본 클래스(Base Class, Super Class, Parent Class)의 메서드를 숨기려면 new 키워드로 파생 클래스 메서드를 선언하기만 하면 됩니다. 따라서 위의 코드는 다음과 같이 다시 작성할 수 있습니다. 즉, 당신이 메소드 숨김을 의도했다면, 메소드를 명시적으로 숨겨야 합니다.
using System;
namespace Polymorphism {
class A {
public void Test() { Console.WriteLine("A::Test()"); }
}
class B : A {
public new void Test() { Console.WriteLine("B::Test()"); }
}
class C : B {
public new void Test() { Console.WriteLine("C::Test()"); }
}
class Program {
static void Main(string[] args) {
A a = new A();
B b = new B();
C c = new C();
a.Test(); // output --> "A::Test()"
b.Test(); // output --> "B::Test()"
c.Test(); // output --> "C::Test()"
a = new B();
a.Test(); // output --> "A::Test()"
b = new C();
b.Test(); // output --> "B::Test()"
Console.ReadKey();
}
}
}
메서드 오버라이딩 : virtual과 override 키워드
C#은 당신의 의도를 중요하게 생각하고 있습니다. 파생 클래스가 메소드를 바꿔 쓰지 않을 것이라면 애초에 일반 메소드로 선언을 하면 되지만 위처럼 A, B, C가 모두 다른 Test()를 가지고 있다고 한다면, 처음부터 그 의도를 담아 재정의를 염두에 두고 virtual로 선언해주면 좋습니다. 버추얼로 선언하면 상속을 받을 때 재정의해도 되고, 정의하지 않아도 부모 메소드를 그대로 호출합니다.
using System;
namespace Polymorphism {
class A {
public virtual void Test() { Console.WriteLine("A::Test()"); }
}
class B : A {
public override void Test() { Console.WriteLine("B::Test()"); }
}
class C : B {
public override void Test() { Console.WriteLine("C::Test()"); }
}
class Program {
static void Main(string[] args) {
A a = new A();
B b = new B();
C c = new C();
a.Test(); // output --> "A::Test()"
b.Test(); // output --> "B::Test()"
c.Test(); // output --> "C::Test()"
a = new B();
a.Test(); // output --> "B::Test()"
b = new C();
b.Test(); // output --> "C::Test()"
Console.ReadKey();
}
}
}
메소드 오버라이딩과 메소드 하이딩
파생 클래스의 메소드는 동시에 virtual과 new가 될 가능성이 있습니다. virtual 키워드와 new 키워드를 사용해, 메소드의 숨기기와 메소드의 오버라이드(override)를 혼재시킬 수도 있습니다. 이것은, 아래와 같이 클래스 C 에서 클래스 B, Test() 메소드를 오버라이드(override) 하고 있기 때문에, 파생 클래스 메소드를 다음의 레벨에 한층 더 오버라이드(override) 하는 경우에 필요합니다.
using System;
namespace Polymorphism {
class A {
public void Test() { Console.WriteLine("A::Test()"); }
}
class B : A {
public new virtual void Test() { Console.WriteLine("B::Test()"); }
}
class C : B {
public override void Test() { Console.WriteLine("C::Test()"); }
}
class Program {
static void Main(string[] args) {
A a = new A();
B b = new B();
C c = new C();
a.Test(); // output --> "A::Test()"
b.Test(); // output --> "B::Test()"
c.Test(); // output --> "C::Test()"
a = new B();
a.Test(); // output --> "A::Test()"
b = new C();
b.Test(); // output --> "C::Test()"
Console.ReadKey();
}
}
}
요약
1. 부모 타입으로 선언된 자식 객체는 new를 쓸 경우(자식은 Hide됨) 부모 클래스의 메소드를 호출하고, override일 경우 자식 메소드를 호출합니다.
2. virtual 키워드는 기본 클래스의 기능을 파생 클래스에서 수정하고 재정의할 수 있도록 하는 데 사용됩니다.
3. override 키워드는 기본 클래스의 기능을 파생 클래스로 확장하거나 변경하는 데 사용됩니다.
4. new 키워드는 기본 클래스의 메서드, 속성, 인덱서 또는 이벤트를 파생 클래스에 숨기는 데 사용됩니다.
'C#' 카테고리의 다른 글
[C#] 박싱(Boxing)과 언박싱(Unboxing) (0) | 2022.09.10 |
---|---|
[C#] 깊은 복사(Deep Copy)와 얕은 복사(Shallow Copy)의 이해 (0) | 2022.09.10 |
[C#] const와 readonly 차이 (0) | 2022.09.10 |
[C#] ref, in, out, params (1) | 2022.09.10 |
[C#] Abstract(추상) vs Virtual(가상) vs Interface(인터페이스) 차이 (0) | 2022.08.30 |
댓글