생성자 내에서는 절대로 가상 함수를 호출하지 말라

 

객체가 완전히 생성되었다고 판단하기 전까지는 절대로 가상 함수를 호출하는 코드는 피해야 한다.

예측할 수 없는 함수가 수행되기 때문이다. 예시 코드를 보여주겠다.

Class Baseclass
{
    protected Baseclass()
    {
    	Func();
    }
    
    protected virtual void Func()
    {
        Console.WriteLine("Base class Func");
    }
}

class Derived : Baseclass
{
	private readyonly string msg = "Before Initializer"
    
    public  Derived(string msg)
    {
        this.msg = msg;
    }
    
    protected override void Func()
    {
    	Console.WriteLine(msg);
    }
    
    public static void Main()
    {
        var d = new Derived("Constructed in main");
    }
}

구조는 이렇다

Baseclass는 가상 함수 Func를 가지고 있다.

Derived는 Baseclass의 파생 클래스이다.

Derived는 Baseclass의 가상 함수 Func를 override 했다.

 

콘솔 창에는 어떤 결과가 출력될까??

1. "Base class Func"

2. "Before Initializer"

3. "Constructed in main"

 

자 우리가 이전에 포스팅했던 객체의 생성 순서를 생각해보자

1. 파생 클래스의 멤버 변수에 대한 초기화 구문 수행

2. 베이스 클래스의 멤버변수에 대한 초기화 구문 수행

3. 베이스 클래스의 생성자.

4. 파생 클래스의 생성자

 

그렇다면 "Base class Func" 이 출력되겠구나!!

 

하지만 정답은 "Before Initializer"이다.

 

이유를 살펴보자. 

1. 베이스 클래스의 생성자를 살펴보면 자기 클래스 내에 정의된 가상 함수를 호출하고 있다.

2. 파생 클래스가 베이스클래스의 가상함수를 재정의 했기 때문에 파생 클래스의 재정의 함수가 호출된다. (런타임에 객체가 파생클래스 이기 때문)

3. msg는 "Before Initializer"인 상태로 Func() 수행

4. msg에 "Constructed in main" 값 저장

 

가상 함수 호출에 대한 방법은 추 후에 좀 더 자세히 살펴보겠다.

위의 코드를 좀더 괜찮은 코드로 바꿔보겠다.

abstract class Baseclass
{
    protected Baseclass()
    {
        Func();
    }
    
    protected abstract void Func();
}

class Derived : Baseclass
{
    private readonly string msg = "before Initializer";
    
    public Derived(string msg)
    {
    	this.msg = msg;
    }
    
    protected override void Func()
    {
    	Console.WriteLine(msg);
    }
    
    public static void Main()
    {
    	var d = new Derived("Constructed in main");
    }
}

위의 코드와 결과는 같지만 코드의 가독성을 높였다.

멤버 변수 msg의 상태는 초기에  "before Initializer"에서 출력 후 "Constructed in main"으로 변했다.

 

이처럼 베이스 클래스의 생성자 내에서 가상 함수를 호출하면 파생 클래스가 가상 함수를 어떻게 구현했는지에 따라 매우 민감하게 동작한다. 파생 클래스는 Client 개발자들이 실제 어떻게 개발할지 모르는 노릇이다. 일반적으로 파생 클래스의 생성자 함수는 2가지로 보는데 첫번째는 매개변수를 이용하여 파생클래스를 초기화 하는 방식 두번째는 매개변수 초기화 구문을 이용해서 초기화를 시키는데 예시와 같은 Baseclass를 작성하면 파생클래스의 제약사항이 많아진다.

 

결론

1. 객체의 생성자 안에서 가상 함수를 호출하지 말자.

2. 호출해도 되는 경우는 매개 변수가 없는 생성자인 상황뿐이다.

3. 그냥 생성자에 가상 함수 쓰지 말자!!!!!! 🤦‍♂️

 

 

 

 

 

 

 

 

'Program > Effective C#' 카테고리의 다른 글

Effective C# ITEM 18  (1) 2020.11.15
Effective C# ITEM 17  (0) 2020.11.08
Effective C# ITEM15  (0) 2020.11.03
Effective C# ITEM14  (0) 2020.10.28
Effective C# ITEM13  (0) 2020.10.28

+ Recent posts