티스토리 뷰
[Effective C# Item 16] 생성자 내에서는 절대로 가상 함수를 호출하지 마라
weekyear 2020. 10. 18. 14:23[Effective C# Item 16]
생성자 내에서는 절대로 가상 함수를 호출하지 마라
객체가 완전히 생성될 때 까지는 가상 함수를 호출하면 이상 동작을 일으킨다. 어떤 타입이든 생성자가 수행을 완료하기 전에는 객체가 완전히 생성되었다고 할 수 없다. 따라서 생성자 내에서 가상 함수를 호출하면 개발자의 예상과는 다르게 코드가 흘러갈 수 있다.
헷갈려~헷갈려~ 어떤 함수가 호출 되는거야?
위 코드에서 콘솔에 어떤 문자열이 출력될 것 같은가?
'Constructed in Main'이라고 생각하는 C# 개발자들이 꽤 될 거라고 생각한다. 하지만 답은 'Set by initializer'다.
베이스 클래스의 생성자를 살펴보면 본인 클래스 내에서 정의된 가상 함수를 호출하고 있다. 하지만 웃기게도 파생 클래스가 가상 함수를 이미 재정의하기 때문에 런타임 중에 파생 클래스의 재정의된 메서드가 호출된다. 왜냐하면 런타임에 객체의 타입이 Derived이기 때문이다.
그럼 호출 순서는 다음과 같다. Derived 객체가 생성되면 베이스 클래스 내의 생성자가 먼저 호출이 되는데 베이스 클래스 내의 생성자는 가상함수 VFunc()을 호출하지만 파생 클래스 Derived의 재정의 된 함수 VFunc()이 호출된다. 이 시점에서 msg는 멤버 초기화 구문으로 정의되어 "Set by initializer"의 값을 가지므로 "Set by initializer"가 출력된다. 그리고 이후에 msg에 "Constructed in main" 값이 할당되지만 이미 msg는 "Set by initializer"로 출력되었다.
이 때문에 객체를 생성하는 동안 가상 함수를 호출하면 개발자의 예상과는 다르게 진행되고 일관성 문제가 발생할 수 있다.
따라서 이번 경우에는 재정의한 가상 함수를 Derived 클래스에서 호출했어야 했다.
수정된 베이스 클래스를 살펴보자.
이 경우 B 타입의 객체를 생성할 수 없기 때문에 파생 클래스에서 반드시 VFunc()을 구현해야 정상적으로 컴파일된다. 이러한 설계 방법이 생성자 내에서 추상 함수를 호출했을 때 런타임 예외를 피할 수 있는 유일한 해법이다.
하지만 C#에서 택한 이 전략은 매우 위험한데 실제로 msg는 변경이 불가능하도록 readonly 변수로 선언했으며, 객체의 전체 수명 동안 동일한 값을 가져야 한다. 하지만 생성자가 작업을 완료할 때까지 잠깐 동안이지만 msg는 의도하지 않게 다른 값으로 변경된다.
또한, 파생 클래스가 어떻게 작성될지 예상할 수는 없는 노릇이므로 베이스 클래스의 생성자 내에서 가상 함수를 호출하게 되면 구조가 매우 취약한 코드가 된다.
따라서, 생성자에서 가상함 수를 호출해도 되는 유일한 경우는 파생 클래스가 기본 생성자만을 정의하고 있고, 다른 어떤 생성자도 가지고 있지 않은 경우뿐이다. 이 경우에만 예상치 못한 동작을 방지할 수 있다.
하지만 이런 방법은 파생 클래스 구현에 커다란 제약을 거는 것이고 이런 규칙에 따라서 사람들이 전부 코드 작성해주리라고도 생각되지 않는다. 이런 규칙을 부여하는 것은 매우 비효율적이다.
따라서 결론 : 생성자 내에서 그냥 가상함수를 호출하지 마!
아직 생성자 내에서 가상 함수 호출 해본 경험이 없어서 이번 파트가 와닿고 그러진 않았는데
기억은 해둬야 겠다.
참조 - Effective C# <강력한 C# 코드를 구현하는 50가지 전략과 기법, 이펙티브>, 빌 와그너, 김명신, 한빛미디어
'Programming > Effective C#' 카테고리의 다른 글
[Effective C# Item 18] 반드시 필요한 제약 조건만 설정하라 (0) | 2020.10.18 |
---|---|
[Effective C# Item 17] 표준 Dispose 패턴을 구현하라 (0) | 2020.10.18 |
[Effective C# Item 15] 불필요한 객체를 만들지 말라 (0) | 2020.10.18 |
[Effective C# Item 14] 초기화 코드가 중복되는 것을 최소화해라 (0) | 2020.10.18 |
[Effective C# Item 13] 정적 클래스 멤버를 올바르게 초기화하라 (0) | 2020.10.18 |