abstract

abstract (C# 참조)

abstract 한정자는 지금 대상이 구현이 빠져 있거나 불완전하다는 걸 알려줘요. 클래스, 메서드, 프로퍼티, 인덱서, 이벤트에 사용할 수 있고요. 클래스 선언에 abstract를 붙이면 그 클래스는 다른 클래스의 기반 클래스로만 쓰이지, 스스로 인스턴스로 만들어지면 안 된다는 뜻이 돼요. 그리고 이 추상 클래스에서 상속받은 비추상 클래스는 abstract로 표시된 멤버를 반드시 구현해야 해요.

추상 클래스는 추상 멤버만 갖는 게 아니에요. 구현이 없어서 파생 클래스에서 반드시 오버라이드해야 하는 추상 멤버와, 일반 메서드·프로퍼티·생성자처럼 완전히 구현된 멤버를 함께 담을 수 있어요. 이렇게 하면 추상 클래스가 공통 기능을 제공하면서도, 특정 추상 멤버는 파생 클래스가 직접 구현하도록 강제할 수 있어요.

[!NOTE] 인터페이스 멤버는 기본적으로 abstract 취급돼요.

추상 멤버와 구현 멤버를 함께 가진 추상 클래스

구현된 메서드와 추상 멤버를 모두 가진 추상 클래스를 하나 볼게요.

:::code language="csharp" source="snippets/shared/Abstract.cs" id="snippet1":::

이 예시에서 Vehicle 추상 클래스가 제공하는 건 크게 나눠요.

  • 구현된 멤버: GetInfo() 메서드, StartEngine() 메서드, 생성자 — 이들은 모든 차량이 공통으로 쓰는 기능이에요.
  • 추상 멤버: Move() 메서드와 MaxSpeed 프로퍼티 — 이들은 각 차량 타입이 자기 방식대로 구현해야 하는 부분이에요.

이런 설계 덕분에 추상 클래스는 공유 기능을 제공하면서도, 파생 클래스가 차량별 동작을 반드시 구현하도록 보장할 수 있어요.

추상 클래스에서 파생된 구체 클래스

이번엔 Square 클래스예요. Shape에서 상속받기 때문에 GetArea를 반드시 구현해야 해요.

:::code language="csharp" source="snippets/csrefKeywordsModifiers.cs" id="1":::

추상 클래스는 다음과 같은 특징이 있어요.

  • 추상 클래스의 인스턴스를 만들 수 없어요.
  • 추상 클래스는 추상 메서드와 접근자(accessor)를 담을 수 있어요.
  • 추상 클래스는 파생 클래스에 기능을 제공하는 구현된 메서드·프로퍼티·필드 같은 멤버도 담을 수 있어요.
  • 추상 클래스에 sealed 한정자는 못 써요. 두 한정자는 정반대 의미를 가지거든요. sealed는 상속을 막고, abstract는 상속을 요구해요.
  • 추상 클래스에서 상속받은 비추상 클래스는 상속받은 모든 추상 메서드와 접근자를 실제로 구현해야 해요.

메서드나 프로퍼티 선언에 abstract를 쓰면 그 멤버가 구현을 담지 않는다는 뜻이 돼요.

추상 메서드는 다음과 같은 특징이 있어요.

  • 추상 메서드는 암시적으로 가상 메서드(virtual method) 예요.

  • 추상 메서드 선언은 추상 클래스에서만 허용돼요.

  • 추상 메서드 선언은 실제 구현을 제공하지 않으니 메서드 본문이 없어요. 선언이 그냥 세미콜론으로 끝나요. 예를 들면:

    public abstract void MyMethod();
    

    구현은 비추상 클래스의 멤버인 메서드 override가 제공해요.

  • class 타입에서는 추상 메서드 선언에 static이나 virtual 한정자를 쓰면 오류예요. 인터페이스에서는 static abstract·static virtual 메서드를 선언할 수 있어요.

    추상 프로퍼티는 선언·호출 구문의 차이만 빼면 추상 메서드와 비슷하게 동작해요.

  • class 타입에서는 static 프로퍼티에 abstract 한정자를 쓰면 오류예요. 인터페이스 선언에서는 static abstract·static virtual 프로퍼티를 선언할 수 있어요.

  • 상속받은 추상 프로퍼티는 파생 클래스에서 override 한정자를 사용하는 프로퍼티 선언으로 오버라이드할 수 있어요.

추상 클래스에 대한 더 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버 문서를 참고해요.

추상 클래스는 인터페이스의 모든 멤버를 구현해야 해요. 인터페이스를 구현하는 추상 클래스는 인터페이스 메서드를 추상 메서드에 매핑할 수 있어요. 예시를 볼게요.

:::code language="csharp" source="snippets/csrefKeywordsModifiers.cs" id="2":::

다음 예시에서 클래스 DerivedClass는 추상 클래스 BaseClass에서 파생돼요. 추상 클래스는 추상 메서드 AbstractMethod 하나와 추상 프로퍼티 X, Y 두 개를 담고 있어요.

:::code language="csharp" source="snippets/csrefKeywordsModifiers.cs" id="3":::

앞의 예시에서 추상 클래스를 이런 식으로 인스턴스화하려고 하면요.

BaseClass bc = new BaseClass();   // Error

컴파일러가 추상 클래스 BaseClass의 인스턴스를 만들 수 없다는 오류가 나요. 그래도 추상 클래스의 생성자는 쓸 수 있어요. 다음 예시에서 확인해 볼게요.

:::code language="csharp" source="snippets/csrefKeywordsModifiers.cs" id="27":::

Shape 클래스는 abstract로 선언되어서 직접 인스턴스화할 수 없어요. 대신 다른 클래스의 설계도 역할을 해요.

  • 추상 클래스의 객체는 못 만들어도, 생성자는 가질 수 있어요. 보통 이 생성자는 protected로 선언해서 파생 클래스만 접근하게 해요. 이 경우 Shape 생성자는 color 매개변수를 받아 Color 프로퍼티를 초기화하고, 콘솔에 메시지를 출력해요. public Square(string color, double side) : base(color) 부분이 기반 클래스(Shape)의 생성자를 호출하면서 color 인자를 넘겨줘요.
  • Shape 클래스의 생성자는 protected Shape(string color)처럼 매개변수를 받도록 정의되어 있어요. 이 말은 C#이 자동으로 기본(매개변수 없는) 생성자를 제공하지 않는다는 뜻이에요. 파생 클래스는 : base(color) 표현으로 기반 생성자를 호출해야 해요. 기본값을 protected Shape(string color="green")처럼 지정하면 파생 클래스에서 : base(color) 표현을 생략할 수 있어요. 이렇게 하면 색은 기본적으로 green으로 설정돼요.

C# 언어 사양

[!INCLUDECSharplangspec]

더 알아보기