[C#] 생성자 - 참조 타입

이정석·2024년 3월 23일

CSharp

목록 보기
20/22
post-thumbnail

참조 타입 생성자

C#에서 참조 타입에 대한 생성자는 아래와 같은 특징을 가진다.

  • 참조 타입의 객체를 생성하면 메모리가 0으로 초기화되고 생성자를 호출한다.
    • 멤버 변수가 0, null로 초기화 된다.
  • 정의된 생성자가 없으면 매개변수가 없는 기본 생성자를 제공한다
    • abstract class는 protected 수준의 생성자를 제공한다.
    • static class는 생성자를 제공하지 않는다.

1. 상속

상속 관계에서 생성차 호출 순서는 어떻게 될까? 아래의 예제코드를 보자.

    class Base
    {
        public Base() { Console.WriteLine("Base"); }
        public Base(int n) { Console.WriteLine($"Base: {n}"); }
    }
    
    class Derived : Base
    {
        public Derived() { Console.WriteLine("Derived"); }
        public Derived(int n) { Console.WriteLine($"Derived: {n}"); }
    }

    public static void Main(string[] args)
    {
        Derived d = new Derived();
    }

메인 함수에서 Derived 객체를 생성하면 자동으로 Base 클래스의 생성자를 호출하게 되는데, 위의 코드의 실행결과는 "Base"가 먼저 출력되고 다음으로 "Derived"가 출력된다.

상속된 객체를 생성할 때, 생성자의 호출 순서의 특징은 다음과 같다.

  • Derived 클래스의 객체를 생성하면 Base 클래스의 생성자가 먼저 호출된다.
  • 컴파일러가 기본적으로 Base 클래스의 인자가 없는 생성자를 호출하도록 코드를 추가한다.
    • Base 클래스에 인자가 없는 생성자가 없을 경우, 호출할 Base 클래스의 생성자를 지정해주어야 한다.

기본적으로 컴파일러는 Derived의 생성자 부분을 아래 코드와 같이 변경하는데 base()에 해당하는 부분을 직접 적어주어도 되고 Base 클래스의 생성자를 직접 호출할 수도 있다.

    class Derived : Base
    {
        public Derived() : base() { Console.WriteLine("Derived"); }
        public Derived(int n) : base() { Console.WriteLine($"Derived: {n}"); }
    }

2. 접근제어자

Base의 생성자가 public이 아니라면 어떻게 될까? private이라면 외부에서 객체를 생성할 수 없을 것이니 protected라면 Derived 클래스에서 Base의 생성자를 호출할 수 있다.

    class Animal
    {
        protected Animal() { }
    }

    class Dog : Animal
    {
        public Dog() { }
    }

    public static void Main(string[] args)
    {
        Animal a = new Animal();    // Error!
        Dog d = new Dog();          // Ok!
    }

위 코드를 보면 메인함수에서 Animal 객체를 직접 생성할 수는 없다. 하지만 Dog 객체는 생성이 가능하다. 즉, Base 클래스의 생성자를 protected로 해놓으면 Base 객체를 생성할 수는 없지만 Base를 상속받은 객체는 생성이 가능하다는 것이다.

3. 가상함수

Base 클래스에 가상함수가 존재하고 Derived 클래스에서 이를 재정의 한다면 어떤 클래스의 함수를 호출하게 되는지 헷갈릴 수 있다. 아래 예제코드를 봐보자.

    class Base
    {
        public Base() { Foo(); }

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

    class Derived : Base
    {
        public int a = 100;
        public int b;

        public Derived() { b = 100; }

        public override void Foo() { Console.WriteLine($"Derived.Foo: {a}, {b}"); }
    }

위와 같은 코드가 있을 때, Derived 객체를 생성하게 된다면 어떻게 될까? 헷갈릴만한 부분이 여러군데가 있다.

  • Derived 생성자에서 Base 생성자가 호출된다면, Foo는 어떤 클래스의 것이 호출이 되는가?
  • Derived의 Foo가 호출된다면 어떤 값이 출력될까?
    • Derived.Foo: 0, 0
    • Derived.Foo: 0, 100
    • Derived.Foo: 100, 0
    • Derived.Foo: 100, 100

실행해보기 전 내 생각에는 100, 100이 출력될 줄 알았는데 실행해보면 100, 0이 출력되는 것을 확인할 수 있다.

컴파일러는 컴파일 단계에서 Derived의 생성자를 다음과 같이 수정하게 되는데 아래 코드를 보면 Base 생성자를 호출하기 전에 초기화되어있는 필드를 초기화 한 뒤에 Base 생성자를 호출하는 것을 볼 수 있다.

    public Derived() {
        a = 100;
        Base();
        b = 100;
    }
profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글