[C#] 클래스

이정석·2024년 1월 22일

CSharp

목록 보기
2/22

클래스

C#에서 클래스를 사용하는 방법은 C++과 살짝 다르다. 아래 코드는 Derived 클래스가 Base 클래스를 상속받는 간단한 구조를 나타낸 코드다.

  class Base
  {
      public int data1 = 10;
      public int data2 = 10;
  }

  class Derived : Base
  {

  }
  
  class Program
  {
      static void Main(string[] args)
      {
          Derived d = new Derived();
          Console.WriteLine(d.data1);
      }
  }

C#의 클래스는 다음과 같은 특징을 가지고 있다.

  1. 각 필드, 메소드마다 접근지정자(public, private, protected)를 지정할 수 있다. 지정하지 않을 경우 기본적으로 private로 설정된다.
  2. 상위 클래스를 상속받을 때 :을 이용하고 기본적으로 public-상속이 적용된다.
  3. 객체 생성시 ()를 붙여주어야 한다. 포인터라는 개념이 없다.

여기서 public-상속은 Base 클래스의 접근지정자를 따른다는 것을 의미하며 Base 클래스의 private 멤버는 Derived 클래스에서도 접근 불가하다.

1. new

Base 클래스에 존재하는 필드(메소드)와 동일한 이름의 필드(메소드)를 만들기 위해서 C#에서 new라는 키워들르 사용할 수 있다. 이는 C++, Java에서 지원하지 않는 키워드로 컴파일러에게 상위 클래스의 멤버를 숨길 수 있다.

  class Base
  {
      public int data1 = 10;
      public int data2 = 10;
  }

  class Derived : Base
  {
      public new int data1 = 20;
  }

new를 꼭 사용해야하는 것은 아니다. new를 사용하지 않으면 실행은 되지만 컴파일 단계에서 경고가 출력된다.

2. 메소드

클래스의 상속관계에서 가장 신경써야할 것중 하나는 바로 '메소드 재정의'이라고 생각한다. 아래 코드는 Base 클래스에서 Foo라는 함수가 있을 때, Derived 클래스에서 Foo라는 함수를 재정의를 한 것처럼 보이는 코드이다.

  class Base
  {
      public void Foo() { Console.WriteLine("Base foo"); }
  }

  class Derived : Base
  {
      public void Foo() { Console.WriteLine("Derived foo"); }
  }

  class Program
  {
      static void Main(string[] args)
      {
          Base b1 = new Base();
          b1.Foo(); // Base foo

          Derived d1 = new Derived();
          d1.Foo(); // Derived foo

          Base b2 = new Derived();
          b2.Foo(); // Base foo? Derived foo?
      }
  }

b1d1의 Foo 호출 결과는 쉽게 예상할 수 있겠지만 b2의 Foo 호출 결과는 어떻게 되는지는 쉽게 예상할 수 없다. 결론부터 말하자면 'Base Foo'가 출력된다. 그 이유는 Derived의 Foo 메소드는 Base의 Foo를 재정의 한 것이 아니기 때문이다.

	Base b2 = new Derived();

위 코드를 다시보면 참조는 Base 클래스이지만 생성된 객체는 Derived 클래스이다. 메소드가 재정의 되어있지 않기 때문에 참조를 보고 메소드를 호출하게 되고 결국 'Base Foo'가 출력된다.

Derived의 Foo는 그 무엇도 재정의한 것이 아니다 그저 Derived의 Foo 메소드를 하나 만들어 놓은 것이다. 물론 new를 앞에 붙이면 컴파일 경고 없이 컴파일 할 수 있지만 new가 없다해도 실행이 안되거나 그러지는 않는다.

3. 가상함수

상속받은 메소드를 재정의하기 위해서는 '가상함수'라는 것을 사용해야 한다. 아래코드는 위의 Base 클래스, Derived 클래스에서 가상함수를 이용해 Foo를 재정의하는 코드이다.

  class Base
  {
      public virtual void Foo() { Console.WriteLine("Base foo"); }
  }

  class Derived : Base
  {
      public override void Foo() { Console.WriteLine("Derived foo"); }
  }

  class Program
  {
      static void Main(string[] args)
      {
          Base b = new Derived();
          b.Foo(); // Derived foo
      }
  }

함수앞에 virtual 키워드를 붙인 메소드는 다음과 같은 의미를 가지게 된다.

이 메소드는 하위 클래스에서 재정의 할 것을 기대하는 메소드이다.

즉 메소드를 상속받고 재정의 하기 위해서 아래 두가지를 만족해야 한다.

  1. Base 메소드를 virtual로 선언해야 한다.
  2. 재정의 할 Derived 메소드에 override를 붙여야한다.

그렇다면, Derived 메소드에 override를 붙이지 않으면 어떻게 될까? 아래 3가지의 경우가 있다.

  class Derived : Base
  {
      // 1)
      public new void Foo() { Console.WriteLine("Derived foo"); }
      
      // 2)
      public void Foo() { Console.WriteLine("Derived foo"); }
      
      // 3)
      public virtual void Foo() { Console.WriteLine("Derived foo"); }
  }

기존의 Foo 메소드에 'new를 붙였을 때', '아무것도 붙이지 않았을 때', 'virtual을 붙였을 때' 어떤 값이 출력될까? 1번과 2번의 결과는 예상하기 쉬운데 'Base foo'가 출력된다. 하지만, 3번의 경우 C++에서 가능하기 때문에 헷갈릴 수 있지만 C#에서는 재정의가 되지 않는다.

세가지 경우 모두 'Base foo'가 출력되는데 C#에서는 반드시 override를 붙여야 메소드 재정의가 이뤄지는 것을 알 수 있다.

profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글