[C#] 13. 추상 클래스(abstract)와 추상 함수(abstract)그리고 가상 함수(virtual)


Study/C#  2020. 7. 14. 18:28

안녕하세요. 명월입니다.


이 글은 C#에서 사용되는 추상 클래스(abstract)와 추상 함수(abstract) 그리고 가상 함수(virtual)에 대한 글입니다.


이전에 제가 클래스 상속, 재정의 하는 방법에 대해 설명한 적이 있습니다.

링크 - [C#] 12. 클래스 상속과 재정의(override)하는 방법과 override와 new의 차이


클래스의 상속은 기본적으로 클래스의 기능을 그대로 이어받아 새롭게 확장 클래스를 만드는 개념입니다. 그러나 이때는 제가 완전한 클래스를 상속 받았습니다.


이번에는 완전한 클래스가 아닌 불완전한 클래스를 만드는 것입니다.

using System;
namespace Example
{
  // 추상 클래스 생성
  abstract class AbstractExample
  {
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력 - GetData함수를 호출한다.
      Console.WriteLine("GetData function - " + GetData());
    }
    // GetData함수는 여기서 정의하지 않고 상속받는 클래스에서 강제 재정의를 명령한다.
    protected abstract string GetData();
  }
  // 추상 클래스를 상속
  class Example : AbstractExample
  {
    // 추상 클래스에서 GetData가 완전히 구현되지 않았기 때문에 상속받는 클래스에서 반드시 재정의해야 한다.
    protected override string GetData()
    {
      // String 반환
      return "Hello world";
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Example 클래스의 인스턴스 생성
      Example ex = new Example();
      // Print 함수 호출
      ex.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보면 AbstractExample의 GetData 함수는 선언만 하고 내용은 구현하지 않았습니다.

그리고 클래스와 함수 앞에 abstract의 키워드를 넣었습니다. 그럼 AbstractExample는 추상 클래스가 되고 GetData는 추상 함수가 됩니다.

추상 클래스는 독자적으로 인스턴스를 생성할 수 없고 상속을 받는 함수는 추상 함수를 모두 재정의(override)해야 합니다.


Example에서 추상 클래스인 AbstractExample를 상속 받고 추상 함수인 override를 재정의(override)했습니다.

이 추상 클래스는 많은 클래스의 공통 부분을 묶어서 공통 클래스(?)를 만드는 데 많이 사용됩니다.

using System;
namespace Example
{
  // 추상 클래스 생성
  abstract class AbstractExample
  {
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력 - GetData함수를 호출한다.
      Console.WriteLine("GetData function - " + GetData());
    }
    // GetData함수는 여기서 정의하지 않고 상속받는 클래스에서 강제 재정의를 명령한다.
    protected abstract string GetData();
  }
  // 추상 클래스를 상속
  class Example1 : AbstractExample
  {
    // 추상 클래스에서 GetData가 완전히 구현되지 않았기 때문에 상속받는 클래스에서 반드시 재정의해야 한다.
    protected override string GetData()
    {
      // String 반환
      return "Example1";
    }
  }
  // 추상 클래스를 상속
  class Example2 : AbstractExample
  {
    // 추상 클래스에서 GetData가 완전히 구현되지 않았기 때문에 상속받는 클래스에서 반드시 재정의해야 한다.
    protected override string GetData()
    {
      // String 반환
      return "Example2";
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 추상 클래스의 지시자로 배열을 선언
      AbstractExample[] array = new AbstractExample[]
      {
        // Example1 클래스의 인스턴스 생성
        new Example1(),
        // Example2 클래스의 인스턴스 생성
        new Example2()
      };
      // 인스턴스를 반복문으로 출력
      foreach (AbstractExample ex in array)
      {
        // 지시자는 AbstractExample(Stack)이지만, Heap에는 Example1또는 Example2의 클래스의 인스턴스가 할당되어 있다.
        ex.Print();
      }

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

이전에 인스턴스 생성과 메모리 할당에 대해 설명한 적이 있습니다.

링크 - [C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null


보시면 지시자를 추상 클래스로 지정(배열)하고 Heap의 메모리를 각 Example1 클래스와 Example2 클래스의 인스턴스를 생성해서 넣었습니다.

Print의 함수는 추상 클래스 AbstractExample를 포함되어 있기 때문에 호출이 가능합니다.

즉, 결과는 인스턴스에서 GetData 함수를 재정의(override)하여 나온 결과로 출력이 됩니다.


이 재정의(override) 꼭 추상 메소드(abstract)만 정의하는 것은 아닙니다.

using System;
namespace Example
{
  // 추상 클래스 생성
  abstract class AbstractExample
  {
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력 - GetData함수를 호출한다.
      Console.WriteLine("GetData function - " + GetData());
    }
    // virtual은 가상 함수, 즉 추상 함수는 내용이 구현되지 않지만, 가상 함수는 구현이 가능
    protected virtual string GetData()
    {
      return "AbstractExample";
    }
  }
  // 추상 클래스를 상속
  class Example1 : AbstractExample
  {
    // 추상 클래스에서 GetData가 완전히 구현되지 않았기 때문에 상속받는 클래스에서 반드시 재정의해야 한다.
    protected override string GetData()
    {
      // String 반환
      return "Example1";
    }
  }
  // 추상 클래스를 상속
  class Example2 : AbstractExample
  {
    // 가상 함수는 반드시 상속이 아니라고 재정의를 하지 않아도 된다.
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 추상 클래스의 지시자로 배열을 선언
      AbstractExample[] array = new AbstractExample[]
      {
        // Example1 클래스의 인스턴스 생성
        new Example1(),
        // Example2 클래스의 인스턴스 생성
        new Example2()
      };
      // 인스턴스를 반복문으로 출력
      foreach (AbstractExample ex in array)
      {
        // 지시자는 AbstractExample(Stack)이지만, Heap에는 Example1또는 Example2의 클래스의 인스턴스가 할당되어 있다.
        ex.Print();
      }

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 추상 클래스는 이전의 구현이 되지 않고 선언만 있던 GetData함수에 구현을 넣고 가상(virtual) 함수로 바꾸었습니다.

이 가상 함수도 역시 재정의가 가능합니다. Example1클래스에서느는 재정의를 하고 구현을 했습니다.

그러나 Example2 함수에서는 재정의를 하지 않았습니다. virtual는 재정의를 하지 않으면 구현된 내용이 실행됩니다.


여기는 이전 글과 조금 겹치는 내용이네요..

링크 - [C#] 12. 클래스 상속과 재정의(override)하는 방법과 override와 new의 차이

프로그램 문법이라는 게 앞과 뒤가 전부 연결되어 있습니다.


여기까지 C#에서 사용되는 추상 클래스(abstract)와 추상 함수(abstract)그리고 가상 함수(virtual)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.