2017년 7월 27일 목요일

C# 소멸자와 Dispose

다음과 같은 C++ 프로그램을 봅시다

#include <iostream>
using namespace std;

class Test
{
    public :
    Test()
    {
        puts(" Constructor");
    }
    ~Test()
    {
        puts(" Destructor");
    }
};

int main()
{
    puts("Program Start");
    {
        Test t;
        puts("  Do with Test");
    }
    puts("Program End");
    return 0;
}
Program Start
 Constructor
  Do with Test
 Destructor
Program End

블럭 안에서 Test를 선언해 사용하면, 블럭을 빠져나갈때 자동으로 소멸자(Detstructor, ~Test())가 호출되어 Test에 대한 마무리를 할 수 있습니다.

이 프로그램을 C#으로 포팅해서 실행해 보겠습니다.

using System;

public class Test
{
    public Test()
    {
        Console.WriteLine(" Constructor");
    }
    ~Test()
    {
        Console.WriteLine(" Destructor");
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Program Start");
        {
            Test t = new Test();
            Console.WriteLine("  Do with Test");
        }
        Console.WriteLine("Program End");
    }
}
Program Start
 Constructor
  Do with Test
Program End
 Destructor

조금 다른 결과가 나왔군요. 소멸자가 호출되긴 하는데, 그 시점이 블럭을 빠져나갈 때가 아니라 프로그램 종료된 후입니다.

C++에서는 t라는 객체가 스택 위에 잡힙니다. 그래서 블록이 닫히고 스택이 해제될 때 해당 객체의 소멸자를 호출할 수 있습니다.
반면 C#의 경우 t 객체는 스택이 아니라 힙(heap) 위에 동적할당의 형태로 잡힙니다. 그래서 스택과는 달리 해제될 시점을 알 수 없는 것이죠.
실제로 C++에서도

        Test *t = new Test();

형태로 동적할당을 하면 블럭을 빠져나갈 때가 아니라 t를 해제할때 소멸자가 호출됩니다.
다만 C#에서는 메모리관리를 프로그래머가 아니라 시스템(Gabage Collection)에서 하므로 메모리해제할 시점을 프로그래머가 확정할 수 없습니다. 위 예제에서처럼 프로그램 종료할 때라든지, 아니면 메모리가 부족해져 가비지 콜렉션을 할때 실행됩니다.

이런 경우 C#에서는 소멸자 대신 Dispose를 사용하는 것이 좋습니다.

using System;

public class Test : IDisposable
{
    public Test()
    {
        Console.WriteLine(" Constructor");
    }
    public void Dispose()
    {
        Console.WriteLine(" Dispose");
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Program Start");
        using(Test t = new Test())
        {
            Console.WriteLine("  Do with Test");
        }
        Console.WriteLine("Program End");
    }
}
Program Start
 Constructor
  Do with Test
 Dispose
Program End

Dispose는 인터페이스인 IDisposable의 멤버이므로 반드시 public으로 정의되어야 합니다.
그리고 using문에서 선언된 객체는 using블럭을 빠져나갈때 그 객체의 Dispose() 메소드를 호출합니다. 그러므로 Dispose()로서 소멸자와 동일한 효과를 낼 수 있습니다.

댓글 없음:

댓글 쓰기