상세 컨텐츠

본문 제목

생성자에서 가상함수 부르면 안되는 이유

똑똑한 개발/C#

by 성댕쓰 2022. 6. 15. 10:49

본문

상황

  1. base class 생성자에서 virtual 함수를 호출함.
  2. derived class 에서 1.에서 호출한 함수를 override 한 상태임. override 한 함수 호출됨.
  3. derived class 생성자가 아직 불리지 않음. 호출된 함수에서 사용하는 리소스를 생성자에서 초기화함. 따라서 null ref exception 발생함.

위처럼 base class 생성자에서 override 가능한 가상함수 호출하면 문제가 발생할 수 있다.
따라서, 만약의 경우를 대비하여 생성자에서 가상함수를 부르지 않도록 하자.

WPF Property System

WPF에서 제공하는 dependency property는 metadata의 콜백함수를 override할 수 있는 기능을 제공한다.
dependecny property SetValue를 호출하면 해당 콜백함수가 호출된다.

예를들어,

public class Aquarium : DependencyObject
{
    ...
    public Aquarium()
    {
        Debug.WriteLine("Base class parameterless constructor running.");

        // Set typical aquarium temperature.
        TempCelcius = 20;

        Debug.WriteLine($"Aquarium temperature (C): {TempCelcius}");
    }
    ...
}

위 base class는 TempCelciusProperty dependency property를 등록했고, 생성자에서 SetValue를 호출한다.

public class TropicalAquarium : Aquarium
{
    ...
    static TropicalAquarium()
    {
        Debug.WriteLine("Derived class static constructor running.");

        // Create a new metadata instance with callbacks specified.
        PropertyMetadata newPropertyMetadata = new(
            defaultValue: 0,
            propertyChangedCallback: new PropertyChangedCallback(PropertyChangedCallback),
            coerceValueCallback: new CoerceValueCallback(CoerceValueCallback));

        // Call OverrideMetadata on the dependency property identifier.
        TempCelciusProperty.OverrideMetadata(
            forType: typeof(TropicalAquarium),
            typeMetadata: newPropertyMetadata);
    }

    // Parameterless constructor.
    public TropicalAquarium()
    {
        Debug.WriteLine("Derived class parameterless constructor running.");
        s_temperatureLog = new List<int>();
    }

     // Property-changed callback.
    private static void PropertyChangedCallback(DependencyObject depObj, 
        DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("Derived class PropertyChangedCallback running.");
        try
        {
            s_temperatureLog.Add((int)e.NewValue);
        }
        catch (NullReferenceException)
        {
            Debug.WriteLine("Derived class PropertyChangedCallback: null reference exception.");
        }
    }
    ...
}

위 derive class는 static 생성자에서 PropertyChangedCallback, CoerceValueCallback을 override한다.

만약, 위 derived class를 생성할 경우, base class 생성자가 먼저 호출 될 것이고, SetValue를 호출하여, 아직 초기화하지 않은 s_temperatureLog를 참조하여 에러가 발생한다.

Safe constructor patterns

ms에서는 다음과 같은 방법으로 해당 문제를 피하라고 제안한다.

  • 생성자에서 overridable 함수를 부르지 않는다.
  • 참조 멤버변수를 사용하기 전에 초기화한다.
    • static 멤버변수를 선언과 동시에 생성한다. ex) List s_temperatureLog = new()
    • derived static constructor에서 초기화 한다. derived static constructor는 base class ctor보다 먼저 호출된다.
    • lazy initialization을 사용한다.
  • override 함수에서 초기화 하지 않은 멤버변수를 사용하지 않는다.


참조 : https://docs.microsoft.com/en-us/dotnet/desktop/wpf/properties/safe-constructor-patterns-for-dependencyobjects?view=netdesktop-6.0

'똑똑한 개발 > C#' 카테고리의 다른 글

[WPF] Command  (0) 2022.04.20
[WPF] Dependency Property Metadata  (0) 2022.04.19
[WPF] Databinding  (0) 2022.04.18
[WPF] Pack uri  (0) 2022.04.15
c# (WPF) Data Binding 에 대해서  (0) 2021.05.31

관련글 더보기

댓글 영역