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


Study/C#  2020. 7. 10. 18:19

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


이 글은 C#에서 클래스 상속과 재정의(override)하는 방법과 override와 new의 차이에 대한 글입니다.


클래스를 만들다 보면 기존에 있는 클래스와 비슷하지만 조금은 다른 클래스를 만드는 경우가 있습니다. 초기 개발이라면 클래스를 수정하거나 맞게 사용하면 됩니다만, 이미 운영 중인 서비스에서 기존의 클래스를 수정하지 안된다고 하면, 새로운 클래스를 만드는 수 밖에 없습니다.

그럴 경우 우리가 가장 쉽게 해결하는 방법은 복사-붙여 넣기로 클래스 명을 달리하여 만들면 됩니다. 그렇게 우리가 10개의 클래스를 복사-붙여 넣기로 클래스를 만들었습니다.

그런데 여기서 가장 최초의 클래스에서 버그가 발생한 것입니다. 그럼 우리는 버그를 수정하기 위해서 똑같은 작업 10번하면 됩니다.

간단한 프로그램이라면 이렇게 하면 됩니다만 사양이 엄청 큰 프로그램이라면... 혹은 10개가 아닌 100개라면... 엄청난 공수가 발생할 것입니다. 참고로 이런 일이 실무에 참 많이 있습니다.


여기서 기존의 있는 클래스를 기능 그대로 가져와서 확장, 수정할 수 있는 프로그램 기능을 프로그램에서는 상속이라고 합니다.

using System;
namespace Example
{
  // 클래스 생성
  class Example
  {
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call Print function");
    }
  }
  // Example 클래스를 상속, 파생 클래스라고 한다.
  class CopyExample : Example
  {
    // 출력 함수
    public void Print2()
    {
      // 콘솔 출력
      Console.WriteLine("Call Print2 function");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      CopyExample clz = new CopyExample();
      // 상속 받으면 상위 클래스의 기능을 다 사용할 수 있다.
      clz.Print();
      // 추가된 함수
      clz.Print2();

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

위 예제를 보면 CopyExample 이름의 클래스는 Example의 클래스를 상속받았습니다. 클래스 상속은 클래스를 생성할 때 옆에 콜론(:) 옆에 클래스 명을 지정하여 해당 클래스를 지정하는 것을 명시합니다.

실제로 Main함수에서 보면 CopyExample 클래스에서는 Example 클래스의 기능을 그대로 사용할 수 있습니다.


클래스를 상속하면 상위 클래스의 기능을 그대로 가져와서 함수나 맴버 변수를 새로 추가할 수 있습니다. 그러나 그대로 가져오지 않고 재정의를 해야 할 때도 있습니다.

using System;
namespace Example
{
  // 클래스 생성
  class Example
  {
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call Print function");
    }
  }
  // Example 클래스를 상속, 파생 클래스라고 한다.
  class CopyExample : Example
  {
    // 출력 함수
    public void Print2()
    {
      // 콘솔 출력
      Console.WriteLine("Call Print2 function");
    }
    // 반환자, 함수명, 파라미터를 같은 이름으로 하면 재정의가 된다. 접근 제한자 뒤에 재정의하는 키워드 new를 넣는다.
    public new void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call new Print function");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      CopyExample clz = new CopyExample();
      // 상속 받으면 상위 클래스의 기능을 다 사용할 수 있다.
      clz.Print();
      // 추가된 함수
      clz.Print2();

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

위 예제에서 CopyExample 클래스에서 new 키워드를 사용해서 함수를 재정의 했습니다.


사실 재정의 키워드는 override입니다. 이 override는 상위 클래스에서 재정의를 허락한 경우(abstract와 virtual)에 사용합니다. 그러나 위 처럼 상위 클래스의 일반 함수를 재정의할 때는 new 키워드를 사용합니다.

using System;
namespace Example
{
  // 클래스 생성
  class Example
  {
    // 출력 함수(가상 함수)
    public virtual void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call Print function");
    }
  }
  // Example 클래스를 상속, 파생 클래스라고 한다.
  class CopyExample : Example
  {
    // 출력 함수
    public void Print2()
    {
      // 콘솔 출력
      Console.WriteLine("Call Print2 function");
    }
    // 반환자, 함수명, 파라미터를 같은 이름으로 하면 재정의가 된다. 접근 제한자 뒤에 재정의하는 키워드 new를 넣는다.
    public override void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call new Print function");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      CopyExample clz = new CopyExample();
      // 상속 받으면 상위 클래스의 기능을 다 사용할 수 있다.
      clz.Print();
      // 추가된 함수
      clz.Print2();

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

위 예제는 Example에서 Print함수에서 virtual 키워드를 넣고 상속 받은 CopyExample 클래스에서 Print를 재정의할 때 override를 사용했습니다.

참고로 virtual가 없는 상태에서 override를 사용하면 에러가 발생합니다.


여기까지의 override와 new의 차이는 이렇습니다만 사실 이 override와 new는 근본적인 차이가 있습니다.

using System;
namespace Example
{
  // 클래스 생성
  class Example
  {
    // 출력 함수(가상 함수)
    public virtual void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call Print function");
    }
  }
  // Example 클래스를 상속
  class CopyExample1 : Example
  {
    // override 재정의
    public override void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call new Print function");
    }
  }
  // Example 클래스를 상속
  class CopyExample2 : Example
  {
    // new 재정의
    public new void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Call new Print function");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Example clz1 = new CopyExample1();
      // 함수 호출
      clz1.Print();

      // 인스턴스 생성
      Example clz2 = new CopyExample2();
      // 함수 호출
      clz2.Print();

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

위 예제에서 Example를 변수 타입으로 CopyExample1와 CopyExample2 클래스의 인스턴스를 생성했습니다.

여기서 override를 한 클래스는 파생 클래스의 함수가 제대로 호출이 되었습니다. 그러나 new를 한 클래스는 Example의 클래스의 함수가 호출이 되었습니다.


다시 정리하면 override는 확실히 재정의가 되었습니다. 즉, Heap에 들어간 인스턴스에 Print함수가 호출이 되었습니다.

그런데 new의 경우는 재정의가 된 것이 아니고 사실 인스턴스 안에는 두가지가 존재하는 것입니다. 즉, 변수 타입을 Example로 하면 Example의 Print함수로 가고 파생 클래스의 CopyExample2를 하게 되면 CopyExample2의 Print함수를 가르키게 됩니다.


여기까지 C#에서 클래스 상속과 재정의(override)하는 방법과 override와 new의 차이에 대한 글이었습니다.


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