[C# 기초강좌] 10. C# 생성자와 소멸자

Posted in SW개발 // Posted at 2023. 11. 2. 08:34
728x90
이 글은 제가 과거에 운영했던 사이트인 http://dotnet.mkexdev.net 의 글을 옮겨온 것입니다. 원본 글은 2010년에 작성되었습니다.

그 전에 운영했었던 사이트(mkex.pe.kr)은 흔적도 없이 사라 졌습니다. 그속의 글들도 모두... 그래서 이 사이트도 사라지기 전에 옮기고 싶은 글을 조금씩 이 블로그로 이동시키려 합니다.
(원본글) http://dotnet.mkexdev.net/Article/Content.aspx?parentCategoryID=1&categoryID=5&ID=674

이 글은 닷넷 기초 지식을 전파하기 위해 2010경에 작성되었으며, 당시 윤성우의 프로그래밍 스터디그룹 네이버 카페에도 필진으로 참여하여 연재했던 글이기도 합니다. 현재 시점에서 조금 달라진 부분이 있을 수 있으나 기본 원리와 언어 기초에 해당하는 부분은 크게 변하지 않았을 것으로 생각하며 이런 부분들을 감안해서 봐 주시기 바랍니다.

 

생성자와 소멸자

 

안녕하세요. 박종명입니다. 닷넷 열 번째 강좌를 진행하도록 하겠습니다. 

이번 강좌는 C# 의 생성자와 소멸자에 대해 알아보겠습니다. 
생성자는 객체가 처음 생성될 때 실행되며 소멸자는 객체가 메모리에서 제거 될 때 실행됩니다


닷넷은 객체의 생성과 소멸 과정에 자동으로 호출되는 두 메커니즘을 제공합니다. 

바로 생성자와 소멸자 입니다

 

 

생성자

생성자는 객체가 new 키워드로 처음 생성될 때 자동으로 호출되는 일종의 메서드입니다.

객체가 생성될 때 실행되기 때문에 일반적으로 객체의 초기화 작업을 할 때 생성자를 많이 이용합니다.

가장 간단한 생성자 예를 보겠습니다

 

class MyClass{

   public MyClass(){

       Console.WriteLine("생성자가 호출되었습니다");

   }

}

 

이렇게 생성자가 정의되어 있으면 다음과 같이 객체를 생성할 때 생성자는 자동으로 호출됩니다

MyClass myClass = new MyClass(); //객체가 생성되면서 생성자가 호출된다

 

생성자의 기본적인 언어적 규칙 다음과 같습니다

- 생성자는 클래스 이름과 동일해야 한다

- 생성자에는 반환 타입을 정의하지 않는다

- 생성자를 (매개변수를 달리 하여) 여러 개 정의할 수 있다(일반적인 메서드 오버로딩이 적용된다)

- 생성자를 정의하지 않으면 기본 생성자(매개변수 없는 생성자)가 자동으로 추가된다

- 생성자는 상속되지 않는다

 

생성자 오버로딩

생성자도 일종의 메서드이기 때문에 메서드의 오버로딩 규칙이 적용됩니다.

아래와 같이 매개변수 타입을 달리 하여 여러 개의 생성자를 정의할 수 있습니다.

 

class MyClass{

   public MyClass(){          

        Console.WriteLine("기본생성자");

   }

   public MyClass(int i){

       Console.WriteLine("int 매개변수 생성자");

   }

   public MyClass(string s){

       Console.WriteLine("string 매개변수 생성자");

   }      

}

 

그리고 다음과 같이 객체를 생성할 때 각각의 생성자가 호출되도록 지정할 수 있습니다.

MyClass myClass1 = new MyClass(); //기본생성자 호출   

MyClass myClass2 = new MyClass(1); //int 매개변수 생성자호출  

MyClass myClass3 = new MyClass("hi"); //string 매개변수 생성자

 

 

생성자 호출 체인(this 키워드)

이렇게 하나의 클래스에 여러 개의 생성자가 있을 경우 this 키워드를 이용하여 생성자끼리 호출할 수 있습니다.

즉 생성자끼리 호출 체인(chain)을 형성할 수 있습니다

 

class MyClass{

   //이 생성자가 호출될 때, int매개변수 생성자가 먼저 호출되도록 한다

   public MyClass() : this(1){          

      Console.WriteLine("기본생성자");

   }

   public MyClass(int i){

      Console.WriteLine("int 매개변수 생성자");

   }         

}

 

 

상속과 생성자

생성자는 상속되지 않습니다.

따라서 생성자에 virtual, new, override, sealed 와 같은 상속과 관련된 키워드를 사용할 수 없습니다.

그리고 상속 구조에서의 자식 객체를 생성하면 부모 객체의 생성자부터 시작하여 아래 방향으로 생성자가 모두 호출됩니다. (부모 객체의 기본 생성자가 자동으로 호출됩니다)

 

class Parent{

   public Parent(){          

       Console.WriteLine("부모생성자");

   }            

}

 

class Child : Parent{

   public Child(){

       Console.WriteLine("자식생성자");

   }

}

 

위와 같이 상속구조로 클래스가 정의된 상태에서 자식 객체를 생성하면(new Child()), 부모 생성자 호출 -> 자식 생성자 호출 순서로 실행됩니다

 

사실 닷넷의 모든 객체는 System.Object 클래스로부터 상속되므로 실제 순서는 다음과 같습니다

 

상속간 생성자 호출 체인(base 키워드)

base 키워드를 이용하여 자식 객체의 생성자에서 부모 객체의 생성자를 명시적으로 호출할 수 있습니다.

아래 코드는 자식 객체 생성자에서 부모 객체 특정 생성자를 명시적으로 호출하는 예 입니다.

 

class Child : Parent{

   public Child() : base(1){

       Console.WriteLine("자식생성자");

   }

}

 

 

구조체에서의 생성자

구조체에서도 생성자 정의가 가능합니다.

다만 매개 변수가 없는 생성자를 명시적으로 정의할 수 없으며 적어도 하나 이상의 매개변수를 가진 생성자에 한해 정의가 가능합니다

 

struct MyStruct{

   public MyStruct(int i){           

       Console.WriteLine("구조체 생성자");

   }

}

 

 

생성자 사용 사례

생성자에 대한 기본적인 사항들을 살펴 보았는데요.

앞서 언급한대로 일반적으로 생성자는 객체의 초기화 작업에 많이 이용됩니다.

그리고 간혹 객체의 생성 제한을 위해서도 사용되는데요. 간단히 그 예를 들어 보겠습니다.

 

생성자 사용 형태 1) 객체의 초기화 작업 수행

객체는 상태와 행위를 가지는 현실 세계의 구조를 지향하는 자료구조입니다

객체가 가지는 상태는 객체의 속성으로 정의됩니다.

이러한 객체의 상태를 초기화 하는 용도로 생성자를 사용하게 됩니다.

 

아래 코드는 사람을 추상화 한 Person 클래스에 나이(age)와 이름(name) 라는 객체의 상태를 초기화 하는데 생성자를 이용한 예입니다

 

class Person{

   int age; string name;

 

   public Person(){

       this.age = 0;

       this.name = string.Empty;

   }    

   public Person(int age, string name){ //외부에서 전달된 값으로 초기화 수행

       this.age = age;  

       this.name = name;

   }

}

 

생성자 사용 형태 2) 객체의 생성 제한

일반적으로 클래스가 정의되면 이 것을 마음껏 객체로 생성해서 사용하기를 원합니다.

그러나 간혹 외부에서 객체 생성 하지 못하도록 해야 할 경우도 있습니다.

대표적으로 객체를 단 하나만 생성하도록 하는 싱글턴(Singleton) 패턴을 들 수 있습니다.

 

생성자를 private 로 선언하면 접근 제한에 걸려 외부에서 객체를 생성할 할 수 없게 됩니다.

싱글턴 패턴에서는 객체가 단 하나만 생성됨을 보장해 주는 디자인 패턴인데요.

대략 아래와 같이 구현 가능합니다.

 

class MyClass{

   static MyClass instance;

 

   private  MyClass() {} //외부에서 객체를 생성하지 못하도록 private 으로 정의

 

   public static MyClass GetInstance() //객체를 대신 생성해 주는 메서드

   {

       if (instance == null){

           instance = new MyClass();

}

 

        return instance;

    }

}

생성자를 private 로 정의하여 외부에서 객체를 생성하지 못하도록 하고 클래스 내부에서 자신의 객체를 반환하는 구조입니다.

 

참고로 위 코드는 다중 쓰레드 환경에서 안전하지 않습니다.

 

 

소멸자

소멸자는 생성자와 정 반대되는 개념입니다

생성자가 객체가 처음 생성될 때 실행되는 메서드라면 소멸자는 객체가 메모리에서 해제 될 때 호출되는 메서드 입니다. 객체가 소멸할 때 처리해야 하는 마무리 작업에 관련된 코드가 기술되는 영역입니다

 

참고로 닷넷 환경에서는 객체를 프로그래머가 명시적으로 해제하지는 못합니다.

GC라고 명명하는 가비지수집기에 의해 객체는 완전히 소멸되는데 이 GC가 객체를 소멸할 때 호출되는 종료 메서드가 바로 소멸자 입니다.

 

객체가 소멸될 때 그 객체의 Finalize 메서드가 호출되는데요. 닷넷에서는 소멸자를 정의하면 컴파일러가 자동으로 소멸자코드를 Finalize 코드로 변환해 줍니다

 

다음과 같이 클래스에 소멸자를 정의하면,

 

class MyClass{

  ~ MyClass { //객체가 소멸될 때 수행하게 될 일련의 해제 과정을 기술함 }

}

 

컴파일 시 암시적으로 다음과 같이 Finalize 메서드로 변환됩니다

 

protected override void Finalize(){

   try{

      일련의 해제 과정을 기술함 .

   }

   finally{

      base.Finalize();

   }

}

 

보통 소멸자는 가비지수집기에 의해 메모리 정리가 되지 않는 비관리 리소스(DBConnection, Stream )를 명시적으로 해제하는데 이용하게 됩니다. 사실 가비지수집과 관리되는 리소스/비관리 리소스에 관한 주제는 사실 중요하고도 방대할 수 있습니다. 관련 자료를 참고하시길 바랍니다.

 

소멸자의 기본적인 언어적 규칙 다음과 같습니다.

- 하나의 클래스에는 단 하나의 소멸자만 정의 가능하다

- 소멸자는 상속되지 않는다

- 소멸자는 개발자의 코드를 통해 명시적으로 호출되지 않는다. GC에 의해 자동 호출 된다

- 소멸자는 ~ 클래스 명 형태로 접근제한자와 매개변수를 가지지 않는다

- 소멸자는 컴파일러에 의해 자동으로 Finalize 메서드로 대체된다

- 구조체에서는 소멸자를 정의할 수 없다

 

 

상속과 소멸자 호출 순서

앞서 살펴본 대로 생성자의 경우 상속 관계에 있을 때 부모 객체 생성자부터 자식객체 생성자로의 호출 순서를 가집니다. 그러나 소멸자는 그 반대입니다

 

즉 상속관계에 있는 객체가 소멸될 때 자식 객체의 소멸자부터 먼저 호출됩니다.

즉 다음과 같이 코드가 정의되었을 경우 자식 객체(Child)가 소멸될 때 자식 소멸자 호출 -> 부모 소멸자 호출 순서로 실행됩니다

 

class Parent{

   ~Parent(){

       Console.WriteLine("부모 소멸자");

   }

}

class Child : Parent{

   ~Child(){

       Console.WriteLine("자식 소멸자");

   }

}

 

그럼. 이것으로 강좌를 마무리 하겠습니다

 

즐거운 한 주 되세요~~