다음과 같은 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++에서도
형태로 동적할당을 하면 블럭을 빠져나갈 때가 아니라 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()로서 소멸자와 동일한 효과를 낼 수 있습니다.