상속(C# 프로그래밍 가이드)
업데이트: 2010년 8월
상속은 캡슐화 및 다형성과 함께 개체 지향 프로그래밍의 세 가지 주요 특징 또는 기둥 중 하나입니다. 상속을 사용하면 다른 클래스에 정의된 동작을 다시 사용, 확장 및 수정하는 새 클래스를 만들 수 있습니다. 멤버가 상속되는 클래스를 기본 클래스라고 하고 해당 멤버를 상속하는 클래스를 파생 클래스라고 합니다. 파생 클래스는 직접 기본 클래스를 하나만 가질 수 있습니다. 그러나, 상속은 전이적입니다. ClassC가 ClassB에서 파생되고 ClassB가 ClassA에서 파생되는 경우 ClassC는 ClassB 및 ClassA에서 선언된 멤버를 상속합니다.
참고
구조체는 상속을 지원하지 않지만 인터페이스는 구현할 수 있습니다. 자세한 내용은 인터페이스(C# 프로그래밍 가이드)을 참조하십시오.
개념적으로 파생 클래스는 기본 클래스를 구체화한 것입니다. 예를 들어 기본 클래스 Animal이 있으면 Mammal이라는 하나의 파생 클래스와 Reptile이라는 또 다른 파생 클래스를 만들 수 있습니다. Mammal은 Animal이고 Reptile도 Animal이지만 각 파생 클래스는 기본 클래스의 서로 다른 특징을 나타냅니다.
다른 클래스를 상속하여 파생 클래스를 정의하는 경우 이 클래스는 암시적으로 생성자와 소멸자를 제외한 기본 클래스의 모든 멤버를 얻습니다. 따라서 파생 클래스는 기본 클래스의 코드를 다시 구현하지 않고 재사용할 수 있습니다. 파생 클래스에서는 멤버를 추가하여 기본 클래스의 기능을 확장할 수 있습니다.
다음 그림에서는 특정 비즈니스 프로세스에서 작업 항목을 나타내는 WorkItem 클래스를 보여 줍니다. 모든 클래스처럼 System.Object에서 파생되고 모든 해당 메서드를 상속합니다. WorkItem은 자신의 다섯 멤버를 추가합니다. 생성자는 상속되지 않으므로 생성자를 포함합니다. 클래스 ChangeRequest는 WorkItem에서 상속하며, 특정 종류의 작업 항목을 나타냅니다. ChangeRequest는 WorkItem 및 Object에서 상속되는 멤버에 둘 이상의 멤버를 추가합니다. 자체 생성자를 추가해야 하며 originalItemID도 추가합니다. 속성 originalItemID를 통해 ChangeRequest 인스턴스를 변경 요청이 적용되는 원본 WorkItem과 연결할 수 있습니다.
클래스 상속
다음 예제에서는 이전 그림에서 설명한 클래스 관계가 C#에서 어떻게 표현되는지 보여 줍니다. 이 예제에서는 WorkItem이 가상 메서드 Object.ToString을 재정의하는 방법 및 ChangeRequest 클래스가 해당 메서드의 WorkItem 구현을 상속하는 방법도 보여 줍니다.
// WorkItem implicitly inherits from the Object class.
public class WorkItem
{
// Static field currentID stores the job ID of the last WorkItem that
// has been created.
private static int currentID;
//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }
// Default constructor. If a derived class does not invoke a base-
// class constructor explicitly, the default constructor is called
// implicitly.
public WorkItem()
{
ID = 0;
Title = "Default title";
Description = "Default description.";
jobLength = new TimeSpan();
}
// Instance constructor that has three parameters.
public WorkItem(string title, string desc, TimeSpan joblen)
{
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = joblen;
}
// Static constructor to initialize the static member, currentID. This
// constructor is called one time, automatically, before any instance
// of WorkItem or ChangeRequest is created, or currentID is referenced.
static WorkItem()
{
currentID = 0;
}
protected int GetNextID()
{
// currentID is a static field. It is incremented each time a new
// instance of WorkItem is created.
return ++currentID;
}
// Method Update enables you to update the title and job length of an
// existing WorkItem object.
public void Update(string title, TimeSpan joblen)
{
this.Title = title;
this.jobLength = joblen;
}
// Virtual method override of the ToString method that is inherited
// from System.Object.
public override string ToString()
{
return String.Format("{0} - {1}", this.ID, this.Title);
}
}
// ChangeRequest derives from WorkItem and adds a property (originalItemID)
// and two constructors.
public class ChangeRequest : WorkItem
{
protected int originalItemID { get; set; }
// Constructors. Because neither constructor calls a base-class
// constructor explicitly, the default constructor in the base class
// is called implicitly. The base class must contain a default
// constructor.
// Default constructor for the derived class.
public ChangeRequest() { }
// Instance constructor that has four parameters.
public ChangeRequest(string title, string desc, TimeSpan jobLen,
int originalID)
{
// The following properties and the GetNexID method are inherited
// from WorkItem.
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = jobLen;
// Property originalItemId is a member of ChangeRequest, but not
// of WorkItem.
this.originalItemID = originalID;
}
}
class Program
{
static void Main()
{
// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
"Fix all bugs in my code branch",
new TimeSpan(3, 4, 0, 0));
// Create an instance of ChangeRequest by using the constructor in
// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
"Add members to the class",
new TimeSpan(4, 0, 0),
1);
// Use the ToString method defined in WorkItem.
Console.WriteLine(item.ToString());
// Use the inherited Update method to change the title of the
// ChangeRequest object.
change.Update("Change the Design of the Base Class",
new TimeSpan(4, 0, 0));
// ChangeRequest inherits WorkItem's override of ToString.
Console.WriteLine(change.ToString());
// Keep the console open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
1 - Fix Bugs
2 - Change the Design of the Base Class
*/
추상 및 가상 메서드
기본 클래스가 메서드를 virtual로 선언하면 파생 클래스는 자체 구현으로 이 메서드를 override할 수 있습니다. 기본 클래스가 멤버를 abstract로 선언하는 경우 해당 클래스를 직접 상속하는 비추상 클래스는 이 메서드를 재정의해야 합니다. 파생 클래스가 추상 클래스이면 추상 멤버를 구현하지 않고 상속합니다. 추상 및 가상 멤버는 개체 지향 프로그래밍의 두 번째 주요 특징인 다형성의 기반이 됩니다. 자세한 내용은 다형성(C# 프로그래밍 가이드)을 참조하십시오.
추상 기본 클래스
new 키워드를 사용하여 직접 인스턴스화하지 않으려는 클래스는 abstract로 선언할 수 있습니다. 이렇게 하면 이 클래스는 새 클래스가 자신에게서 파생되는 경우에만 사용될 수 있습니다. 추상 클래스는 추상으로 선언된 하나 이상의 메서드 시그니처를 포함할 수 있습니다. 이러한 시그니처는 매개 변수와 반환 값을 지정하지만 구현(메서드 본문)은 포함하고 있지 않습니다. 추상 클래스가 추상 멤버를 포함할 필요는 없지만 클래스에 추상 멤버가 있는 경우 해당 클래스는 반드시 추상으로 선언되어야 합니다. 추상 클래스가 아닌 파생 클래스는 추상 기본 클래스의 모든 추상 멤버에 대한 구현을 제공해야 합니다. 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드) 및 추상 클래스 디자인을 참조하십시오.
인터페이스
인터페이스는 추상 멤버로만 구성된 추상 기본 클래스와 어느 정도 유사한 참조 형식입니다. 클래스가 인터페이스를 구현하는 경우 해당 인터페이스의 모든 멤버에 대한 구현을 제공해야 합니다. 클래스는 하나의 직접 기본 클래스에서 파생되지만 여러 개의 인터페이스를 구현할 수 있습니다.
인터페이스를 사용하여 "is a" 관계가 없을 수도 있는 클래스를 위한 특정 기능을 정의할 수 있습니다. 예를 들어 System.IEquatable<T> 인터페이스는 클라이언트 코드에서 해당 형식의 같음에 대한 정의에 따라 두 개체가 같은지 여부를 결정할 수 있어야 하는 클래스 또는 구조체에서 구현할 수 있습니다. IEquatable<T>은 기본 클래스 및 파생 클래스 사이에 존재하는 같은 종류의 "is a" 관계를 의미하는 것이 아닙니다(예: Mammal은 Animal). 자세한 내용은 인터페이스(C# 프로그래밍 가이드)을 참조하십시오.
기본 클래스 멤버에 대한 파생 클래스 액세스
파생 클래스는 기본 클래스의 public, protected, internal 및 protected internal 멤버에 액세스할 수 있습니다. 파생 클래스는 기본 클래스의 private 멤버를 상속함에도 불구하고 해당 멤버에 액세스할 수 없습니다. 하지만 모든 private 멤버는 파생 클래스에 존재하면서 기본 클래스에서 수행하는 작업과 동일한 작업을 수행할 수 있습니다. 예를 들어 protected 기본 클래스 메서드가 private 필드에 액세스한다고 가정합니다. 상속된 기본 클래스 메서드가 올바르게 작동하려면 파생 클래스에 해당 필드가 있어야 합니다.
추가 파생 방지
클래스는 자신 또는 멤버를 sealed로 선언하여 다른 클래스가 상속하는 것을 막을 수 있습니다. 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드)을 참조하십시오.
파생 클래스에서 기본 클래스 멤버 숨기기
파생 클래스는 기본 클래스와 동일한 이름과 시그니처를 가진 멤버를 선언하여 기본 클래스 멤버를 숨길 수 있습니다. new 한정자를 사용하면 멤버가 기본 클래스의 재정의가 아니라는 것을 명시적으로 나타낼 수 있습니다. new를 반드시 사용할 필요는 없지만 new를 사용하지 않으면 컴파일러에서 경고를 생성합니다. 자세한 내용은 Override 및 New 키워드를 사용하여 버전 관리(C# 프로그래밍 가이드) 및 Override 및 New 키워드를 사용해야 하는 경우(C# 프로그래밍 가이드)을 참조하십시오.
참고 항목
참조
개념
변경 기록
날짜 |
변경 내용 |
이유 |
---|---|---|
2010년 8월 |
더 쉽게 이해할 수 있도록 예제를 단순화하고 설명을 추가하였습니다. |
고객 의견 |