일반적으로 Desturctor는 소멸자라는 뜻을 가지고 있으며, 생성자와는 반대의 개념으로 사용됩니다. 소멸자는 클래스의 객체가 소멸될 때 호출되어집니다. 객체 생성시 할당했던 메모리를 소멸자로 하여금 회수하도록 하는 것입니다.
C++과 마찬가지로 소멸자는 매개변수를 가질수 없을 뿐더러 접근 제한자를 가질수 없고, 명시적으로 호출을 될 수도 없습니다. 소멸자는 생성자의 호출 순서와는 반대 되는 순서로 호출이 되어지는데요, 아래 예제 코드를 통해 살펴보도록 하겠습니다.
소멸자는 자신이 만든 클래스면 앞에 ~를 붙여 함수를 구현 합니다. 예를들어 사용자가 구현한 클래스의 이름이 MyClass일 경우 다음과 같이 구현 할 수 있습니다.
~ MyClass()
{
// 이부분에 메모리를 회수하는 작업을 구현합니다.
}
간단한 예제를 통해 소멸자에 대해 좀더 알아보도록 하겠습니다.
using System;
class A
{
public A() { Console.WriteLine("Creating A"); }
~A() { Console.WriteLine("Destroying A"); }
}class B : A
{
public B() { Console.WriteLine("Creating B"); }
~B() { Console.WriteLine("Destroying B"); }}
class C : B
{
public C() { Console.WriteLine("Creating C"); }
~C() { Console.WriteLine("Destroying C"); }
}
class App
{
public static void Main()
{
C c = new C();
Console.WriteLine("Object Created ");
Console.WriteLine("Press enter to Destroy it");
Console.ReadLine();
c = null;
Console.Read();
}
}
위와 같이 3개의 A,B,C라는 클래스가 존재한다고 할때 실행 결과는 어떻게 될가요?
정답은 알아 내셨나요?? 정답을 공개하자면 아래와 같은 결과가 나타납니다.
정답은 맞추셧나요? 한번에 정답을 맞추셧다면, C#의 특성 & OOP의 개념을 잘 이해하고 계신 분이십니다.
(아래 이유를 정리 해 놓았으니 보시고 이해한 답과 맞는지 비교해 보시기 바랍니다.)
그리고 틀리셨다면, 그 이유는 무엇일까요??
몇몇 분들에게 질문을 드려봤었지만, 정답을 틀리신 대부분의 사람들은 아래와 같은 결과를 예상했었는데요. New를 통해 객체를 생성하고, 이를 다시 NULL로 없애 버렸기 때문에 소멸자가 호출된다. 라고 생각을 하셨기때문입니다.
그렇다면 왜 저렇게 나오는 것일가요? New를 호출 했는데 Delete를 안해서 소멸자가 호출되지 않은걸가요??
(C++을 주로 사용하시는 분들께서는 이렇게 대답을 하시지만, 정답이 아닙니다.)
C#에서는 소멸자 호출 시점이 고정되어 있지 않기 때문입니다, C#에서는 효율적인 메모리 관리를 위해 가비지 콜렉터(Garbage Collector)라는 기능을 제공하고 있습니다. 가비지 콜렉터란 쉽게 생각해서 쓰레기 처리기 정도로 생각을 하실 수가 있는데요. New등을 통해 선언된 메모리가 더이상 사용되지 않는 쓰레기 상태가 되었을때 알아서 없애 준다는 계념입니다. (이때 소멸자가 호출이 되는거죠,)
이로인해 개발자는 메모리를 할당 놓고 사용만 하면되고, 해제하는데에 일일이 찾아 해제를 하지 않아도 자동으로 해제가 된다는 것입니다. 그럼 어떻게 가비지 컬렉터가 작동을 하는 것일가요?
제가 예전에 김역욱 MVP님 세미나를 들으면서 가비지 컬렉터에 대한 내용을 들은적이 있는데 재미있는 내용같아 이야기 해드리겠습니다.
메모리를 하나의 기숙사라고 생각합시다. 그리고 그 기숙사에 살고있는 사람들이 우리가 생성해놓은 객체라고 하겠습니다. 그리고 마지막으로, 기숙사생들을 관리하는 사감이 있습니다. 보통 우리는 어떤 할일이 있을때는 깨어있고, 할일이 없으면 잠을 잡니다. 그리고 사감은 현재 기숙사의 상황을 체크하기 위해서 기숙사를 돌면서 자고 있는 사람들을 체크합니다.
기숙사에서 생활하고 있는사람들 한명 한명 한명 돌아가면서, "야~ 자냐~?" 하고 물어봅니다.
(당연히 자고 있는 사람이라면 대답을 하지 않겠죠??ㅎ) 만약에 그렇게 물어봤는데 "나지금 안자요~" 라고 대답을 한다면, 넘어가고 쥐죽은듯이 자고 있는 사람들은 기록을 합니다.
그리고 다음번에 체크를 할 때는, 아까 자고 있던 사람들 한테만 가서 또 같은 방법으로 "야~자냐~?" 라고 물어봅니다. 이와 같은 방법으로 약 3번정도 물어본 뒤 진짜로 자고 있는 사람들은 가차없이 "죽여" 버립니다. -_-
조금 섬뜩한 내용이긴 하지만, 이와 같은 방식으로 현재 사용중인 메모리와 비 사용중인 메모리를 구분 지음으로서, 불필요한 메모리 사용을 줄이게 되는 것입니다.
다시 코드로 돌아와서, 코드를 살펴 보겠습니다.
C c = new C();
Console.WriteLine("Object Created ");
Console.WriteLine("Press enter to Destroy it");
Console.ReadLine();
c = null;
Console.Read();
위 코드를 보시면,
c = null;
이라는 부분을 찾을 수 있는데요, 맨 윗줄 new C()를 통해 변수 c에 새로운 객체 C가 저장되었지만, null이 들어옴과 동시에 이전에 저장되어 있던 객체가 방황을 하게 됩니다. -_- (나는 누구인가..) 그러다가 할일이 없으면 잠이 들겠죠? 하지만 잠이 들었다고 해서 바로 객체가 소멸하는것이 아니라, 위에서 설명 했듯이 가비지콜렉터가 자는 메모리를 죽여(-_-)줘야지만 소멸자가 호출됩니다.
그래서, null이 들어간 직후에는 소멸자가 호출이 되지 않은 것입니다.
그렇다면 소멸자를 호출하게 만드는 방법은 없을가요?
네 있습니다. 바로 가비지 컬렉터를 직접 호출하도록 해주는 방법인데요, .Net Framework에서는 가비지 컬렉터를 GC라는 이름의 클래스로 제공 사용합니다. 직접 가비지 컬렉터를 호출 해주기 위해서는 GC 클래스에 Static으로 선언된 Collect 함수 호출을 통해 가비지 컬렉터의 동작을 명령 할 수 있습니다.
그럼 코드를 살짝 바꿔 아래와 같이 작성해 보겠습니다.
C c = new C();null을 대입한 이후 바로 Collect를 호출함으로서 쓸모 없는 메모리를 해제 하게끔 합니다.
Console.WriteLine("Object Created ");
Console.WriteLine("Press enter to Destroy it");
Console.ReadLine();
c = null;
GC.Collect();
Console.Read();
위와 같이 코드를 살짝 변경한 뒤 실행 한 결과는 아래와 같습니다.
조금 이해가 되셨나요? 가비지 컬렉터는 메모리를 알아서 관리 해주기때문에, 편리하기도 하지만, 그렇다고 너무 가비지 컬렉터에 의존하다 보면 오히려 프로그램의 성능을 저하 시킬 수 있으니 유의하시기 바랍니다.
(메모리가 커지면 커질 수록 관리 대상 메모리가 많아짐으로 성능저하를 유발할 수 있습니다. )
김대욱(kdw234@naver.com) http://kdw234.tistory.com

