생성자(Constructor)

  • 24 minutes to read

C#에서 생성자(constructor)는 클래스에서 제일 먼저 호출되는 메서드로, 클래스의 기본값 등을 설정하는 역할을 합니다. 자동차 클래스를 예로 들면 자동차의 시동 걸기에 해당하는 것이 바로 생성자입니다.

> // 생성자: 클래스가 사용될 때 제일 먼저 호출되는 메서드로 클래스의 기본값 등을 설정하는 역할

생성자(Constructor)

클래스의 구성 요소 중에는 생성자(constructor)라는 메서드가 있습니다. 단어 그대로 개체를 생성하면서 무엇인가를 하고자 할 때 사용되는 메서드입니다. 일반적으로 생성자는 개체를 초기화(주로 클래스 내의 필드를 초기화)하는 데 사용됩니다. 생성자는 생성자 메서드라고도 부릅니다. 이러한 생성자는 독특한 규칙이 있는데, 바로 생성자 이름이 클래스 이름과 동일하다는 것입니다. 클래스 내에서 클래스 이름과 동일한 이름을 갖는 메서드는 모두 생성자입니다.

생성자는 매개변수가 없는 기본(default) 생성자가 있고, 원한다면 매개변수를 원하는 만큼 정의해서 사용할 수도 있습니다. 이때 반환값은 가지지 않습니다. 또한, 생성자도 static 생성자(정적 생성자)와 public 생성자(인스턴스 생성자)로 구분됩니다. 일반적으로 public 키워드를 사용하는 인스턴스 생성자가 많이 사용됩니다.

NOTE

참고로, Visual Basic에서는 생성자는 New 메서드입니다. 이름에서 의미하듯이 생성자는 새롭게(New) 개체를 생성할 때 호출되는 메서드입니다.

생성자 만들기

생성자에 대한 간단한 데모 코드를 살펴보겠습니다. 다음 코드는 Car 클래스내에 Car() 이름의 인스턴스 생성자를 만들었습니다. 이처럼, 생성자는 클래스 안에서 클래스 이름과 동일한 이름의 메서드로 만듭니다. 생성자는 다른 메서드와 달리 반환값을 지정하지 않습니다. 생성자는 메서드와 동일하게 어떠한 기능을 수행합니다. 그 기능은 클래스의 멤버의 값을 초기화하는 역할을 주로 합니다. 다음 코드는 화면에 "Constructor" 문자열을 출력하는 기능만 합니다.

> class Car
. {
.     public Car()
.     {
.         Console.WriteLine("Constructor");
.     }
. }

Car 클래스의 Car() 생성자는 Car 클래스의 인스턴스가 만들어질 때 자동으로 호출되어 실행됩니다.

> var car = new Car();
Constructor

위 코드처럼 Car 클래스의 인스턴스가 생성될 때 자동으로 Car() 생성자가 실행됩니다.

메서드와 마찬가지로 클래스에 매개 변수를 달리하여 여러 개의 생성자를 만들 수 있습니다. 메서드를 학습할 때 다시 언급하겠지만, 메서드가 오버로드(다중정의) 되는 것처럼 생성자도 오버로드가 됩니다. 이를 생성자 오버로드 또는 생성자 오버로딩이라고 합니다.

또한 this 키워드를 통해서 다른 생성자를 호출할 수 있습니다.

생성자를 사용하여 개체 생성

생성자를 사용하여 개체를 초기화할 수 있습니다. 생성자 메서드는 클래스의 인스턴스인 개체가 생성될 때 추가적으로 실행하고자 하는 코드를 실행시켜줄 수 있는 역할을 합니다. 이러한 역할을 개체를 초기화한다고 줄여서 말합니다. 프로젝트 코드로 생성자 예제를 살펴보겠습니다.

코드: ConstructorDemo.cs

using System;

// 클래스
class ConstructorDemo
{
    // 생성자
    public ConstructorDemo()
    {
        Console.WriteLine("생성자가 호출되었습니다.");
    }

    // 진입점
    static void Main()
    {
        ConstructorDemo c = new ConstructorDemo();
    }
}
생성자가 호출되었습니다.

위 코드의 ConstructorDemo() 생성자는 기본 생성자입니다. 이러한 기본 생성자는 매개 변수가 없습니다. 또한 클래스내에 생성자를 선언하지 않으면 기본적으로 기본 생성자가 내부적으로 만들어집니다. 물론 아무 의미 없는 생성자이긴 합니다.

기본 생성자 만들기

매개 변수도 없고 반환값도 없는 기본 생성자를 한번 더 만들어 보겠습니다.

코드: ConstructorMethod.cs

//[?] 생성자: 클래스 이름과 동일한 이름의 반환값이 없는 메서드
using System;

class Student
{
    public Student()
    {
        Console.WriteLine("Student 개체가 생성됩니다.");
    }
}

class ConstructorMethod
{
    static void Main()
    {
        Student student;
        student = new Student(); // 생성자를 통해서 개체를 생성
    }
}
Student 개체가 생성됩니다.

생성자도 메서드(함수)입니다. 생성자를 생성자 메서드 라고도 부릅니다. 모든 클래스는 적어도 하나의 생성자를 갖습니다. 단, 사용하지 않는 기본 생성자는 코드에서 생략할 수 있습니다. 위 코드의 Student() 생성자 메서드는 Student 클래스 이름과 동일한 이름의 메서드입니다. 클래스의 인스턴스인 student 개체가 생성될 때 자동으로 실행됩니다. 생성자는 void를 포함한 반환값을 가지지 않습니다.

매개 변수가 있는 생성자 만들기

매개 변수가 있는 생성자를 만들어 보겠습니다. 다음 코드는 ClassAndInstance.cs 파일에 있습니다.

코드: ClassAndInstance.cs

> //[?] 클래스는 Type, 종류, 분류 정의
. //[1] Dog 클래스
. public class Dog
. {
.     //[2] name 필드
.     private string name;
.     //[3] name 매개변수를 받아서 name 필드에 저장하는 생성자
.     public Dog(string name)
.     {
.         this.name = name; // 넘겨온 name을 name 필드에 임시 저장
.     }
.     //[4] name 필드의 값을 출력하는 반환값이 있는 메서드
.     public string Cry()
.     {
.         return name + "이(가) 멍멍멍";
.     }
. }
> 
> //[?] 인스턴스/개체/객체는 실체, 구체적인 것 정의
> //[5] Dog 클래스 사용:  happy, worry => 인스턴스, 개체, ...
> Dog happy = new Dog("해피");
> happy.Cry()
"해피이(가) 멍멍멍"
> Dog worry = new Dog("워리");
> worry.Cry()
"워리이(가) 멍멍멍"

일반적으로 매개 변수가 있는 생성자는 클래스내에 선언된 특정 필드의 값을 초기화하는 목적으로 많이 사용됩니다.

매개 변수가 여러 개인 생성자 만들기

매개 변수가 여러 개인 생성자를 만들어보겠습니다.

코드: ConstructorParameter.cs

// 생성자 매개 변수를 사용하여 클래스의 멤버(필드) 초기화
using System;

namespace ConstructorParameter
{
    class My
    {
        private string _name;
        private int _age;
        public My(string name, int age)
        {
            this._name = name; // this.필드 = 매개 변수
            this._age = age;
        }
        public void PrintMy()
        {
            Console.WriteLine($"이름: {this._name}, 나이: {this._age}");
        }
    }

    class ConstructorParameter
    {
        static void Main()
        {
            My my = new My("홍길동", 21);
            my.PrintMy(); // 이름: 홍길동, 나이: 21
        }
    }
}
이름: 홍길동, 나이: 21

생성자의 매개 변수로 nameage를 선언하였고 이 값을 통해서 My 클래스의 _name_age 필드를 초기화할 수 있습니다.

참고로 클래스 내에서 생성자를 자동으로 만들어주는 코드 조각(단축키)은 ctor과 탭을 두번 입력하면 됩니다.

매개변수가 있는 생성자를 사용하여 원의 넓이를 구하는 프로그램 만들기

생성자로 원의 반지름을 받아서 원의 넓이를 구하는 코드는 다음과 같이 작성할 수 있습니다.

코드: CircleClass.cs

> //[?] 매개변수가 있는 생성자를 사용하여 원의 넓이를 구하는 프로그램
> // Circle.cs
> public class Circle
. {
.     private int _radius;
. 
.     public Circle(int radius)
.     {
.         _radius = radius;
.     }
. 
.     public double GetArea()
.     {
.         // 원의 면적을 구하는 공식
.         return Math.PI * _radius * _radius; 
.     }
. }
> 
> // CircleClass.cs
> var circle1 = new Circle(10); 
> circle1.GetArea()
314.15926535897933
> var circle2 = new Circle(5);
> circle2.GetArea()
78.539816339744831

클래스에 생성자 여러 개 만들기

클래스에는 매개 변수를 달리하여 생성자를 여러 개 만들 수 있습니다. 이러한 기능을 생성자 오버로드(Constructor Overload)라고 합니다.

이번에는 생성자 오버로드를 사용해보겠습니다.

코드: ConstructorOverload.cs

using System;

class ConstructorLog
{
    public ConstructorLog()
    {
        Console.WriteLine("기본 생성자 실행");
    }

    public ConstructorLog(string message)
    {
        Console.WriteLine("오버로드된 생성자 실행 : " + message);
    }
}

class ConstructorOverload
{
    static void Main()
    {
        ConstructorLog log1 = new ConstructorLog();
        ConstructorLog log2 = new ConstructorLog("C#");
        ConstructorLog log3 = new ConstructorLog("ASP.NET");
    }
}
기본 생성자 실행
오버로드된 생성자 실행 : C#
오버로드된 생성자 실행 : ASP.NET

메서드와 마찬가지로 매개 변수를 달리하여 하나의 클래스에 여러 개의 생성자를 만들 수 있습니다. 이러한 생성자 오버로드(Overload) 기능을 사용하면 동일 클래스로 다양한 데이터를 받아서 처리할 수 있습니다. 매개변수가 없는 생성자는 기본 생성자라고하며 매개변수가 지정된 생성자가 있으면 해당 매개변수가 지정되면 실행되는 형태입니다.

필드, 생성자, 메서드를 함께 사용하기

클래스의 구성 요소들인 필드 생성자, 메서드를 모두 만들고 사용해 보겠습니다.

코드: ConstructorNote.cs

> //[?] 생성자: 클래스 이름과 동일한 메서드, 클래스의 필드를 초기화하는 역할
> public class Person
. {
.     //[1] 필드
.     private string name;
.     //[2] 매개변수가 없는 생성자
.     public Person()
.     {
.         name = "홍길동";
.     }
.     //[3] 매개변수가 있는 생성자
.     public Person(string n)
.     {
.         name = n;
.     }
.     //[4] 메서드: 이름의 값을 외부에 공개
.     public string GetName()
.     {
.         return name;
.     }
. }
> 
> //[1] 매개변수가 없는 기본 생성자 호출
> Person saram1 = new Person();
> saram1.GetName()
"홍길동"
> var saram2 = new Person();
> saram2.GetName()
"홍길동"
> 
> //[2] 매개변수가 있는 생성자 호출
> Person person1 = new Person("백두산");
> person1.GetName()
"백두산"
> var person2 = new Person("임꺽정");
> person2.GetName()
"임꺽정"

클래스의 주요 구성요소들의 특징을 엿볼 수 있는 예제입니다. 필드는 클래스내에서 부속품 역할로 현재 예제에서는 이름을 저장해 놓는 공간으로 사용됩니다. 생성자 중 매개 변수가 없는 생성자는 필드의 값을 기본 값으로 초기화하는 역할을 하며 매개 변수가 있는 생성자는 개체를 생성할 때 넘겨 준 문자열로 초기화합니다. 메서드는 필드의 값을 외부에 공개하거나 직접 출력할 때 사용됩니다.

정적 생성자와 인스턴스 생성자

생성자도 정적 생성자와 인스턴스 생성자로 구분될 수 있습니다. 클래스의 정적 멤버를 호출할 때 제일 먼저 호출되는 정적 생성자는 static 키워드로 생성자를 만들로 인스턴스 생성자는 public 키워드로 생성자를 만듭니다. 이번에는 생성자의 여러 가지 종류를 사용해보겠습니다.

코드: ConstructorAll.cs

using System;

namespace ConstructorAll
{
    public class Person
    {
        private static readonly string _Name;
        private int _Age;

        //[!] 생성자 종류: 정적(static)과 인스턴스(instance)
        //[1] 정적 생성자
        static Person() { _Name = "홍길동"; }
        //[2] 인스턴스 생성자: 매개 변수가 없는 생성자
        public Person() { _Age = 21; }
        //[3] 인스턴스 생성자: 매개 변수가 있는 생성자
        public Person(int _Age)
        {
            this._Age = _Age; // this.필드 = 매개 변수;
        }

        //[4] 정적 메서드
        public static void Show()
        {
            Console.WriteLine("이름 : {0}", _Name);
        }
        //[5] 인스턴스 메서드
        public void Print()
        {
            Console.WriteLine("나이 : {0}", _Age);
        }
    }
    class ConstructorAll
    {
        static void Main()
        {
            //[A] 정적 생성자 실행
            Person.Show(); // 정적인 멤버 호출

            //[B] 인스턴스 생성자 실행
            (new Person()).Print(); // 인스턴스 멤버 호출
            (new Person(22)).Print();
        }
    }
}
이름 : 홍길동
나이 : 21
나이 : 22

[1]번 코드와 같이 static이 붙는 생성자는 정적 생성자로 [A]에서처럼 정적인 멤버가 호출될 때 먼저 실행됩니다. [2]번과 [3]번 코드에서는 인스턴스 생성자를 선언했고 [B]에서처럼 인스턴스 멤버가 호출될 때 실행됩니다.

생성자 코드도 메서드의 일종입니다. 하지만, 메서드와는 달리 반환 형식을 지정할 수 없습니다. 또한 static 생성자는 매개 변수를 포함할 수 없으며, 매개 변수를 통한 필드를 초기화 하고자 할 때에는 인스턴스(instance) 생성자인 public 생성자를 사용하여야 합니다.

this() 생성자로 다른 생성자 호출하기

생성자에서 this()는 나 자신의 또 다른 생성자를 의미한다. this() 생성자로 매개 변수가 있는 생성자에서 매개 변수가 없는 생성자 호출하거나 또 다른 생성자들을 호출 가능합니다.

코드: ConstructorThis.cs

> class Say
. {
.     private string message = "[1] 안녕하세요.";
.     public Say() => Console.WriteLine(this.message);
.     //[1] this() 생성자로 나 자신의 매개변수가 없는 생성자 먼저 호출
.     public Say(string message) : this()
.     {
.         //[2] 매개변수가 있는 생성자 자체도 호출 
.         this.message = message;
.         Console.WriteLine(this.message);
.     }
. }
> 
> //[A] 매개변수가 있는 생성자 호출할 때 매개변수가 없는 생성자도 함께 호출
> new Say("[2] 잘가요.");
[1] 안녕하세요.
[2] 잘가요.

[1]번 코드에서 매개 변수가 있는 생성자 뒤에 콜론(:) 기호와 this()를 사용하여 나 자신의 매개 변수가 없는 생성자를 먼저 호출하는 코드 모양을 볼 수 있습니다. 그런 후 다시 매개 변수가 있는 생성자를 호출할 수 있습니다.

생성자 포워딩(Forwarding)

this() 생성자를 사용하면 생성자를 포워딩(Forwarding)할 수 있으므로 다른 생성자에게 값을 전달하기가 좋습니다.

코드: ConstructorForwarding.cs

> class Money
. {
.     public Money() : this(1000) { } // 아래 생성자로 전송
.     public Money(int money) => Console.WriteLine("Money: {0:#,###}", money);
. }
> 
> var basic = new Money(); // 1000
Money: 1,000
> var bonus = new Money(2000); // 2000
Money: 2,000

생성자 뒤에 오는 this()는 나 자신의 또 다른 생성자를 의미합니다. 이런 형태로 다른 생성자로 값을 전달할 수 있습니다. 나중에 상속을 배우게 되면 this()와 비슷하게 base()를 사용하여 부모 클래스의 생성자에게 값을 전달할 수도 있습니다.

생성자를 사용하여 읽기 전용 필드 초기화

필드를 정의할 때 readonly 키워드를 붙일 수 있습니다. 이렇게 만들어진 필드를 읽기 전용 필드라고 합니다. 읽기 전용 필드는 처음 선언할 때 직접 초기화를 하거나 나중에 초기화할 때에는 클래스의 생성자를 통해서만 초기화가 가능합니다. 생성자를 통해서 초기화된 후에는 상수와 마찬가지로 값을 변경할 수 없습니다.

코드: ReadOnlyNote.cs

> //[?] 생성자를 사용하여 읽기 전용 필드 초기화
> public class WhitchService
. {
.     // 읽기 전용 필드
.     private readonly string _serviceName;
.     public WhitchService(string serviceName)
.     {
.         // 읽기 전용 필드는 생성자에 의해서 초기화해서 사용 가능
.         _serviceName = serviceName;
.     }
.     public void Run() => Console.WriteLine($"{_serviceName} 기능을 실행합니다.");
. }
> 
> var file = new WhitchService("[1] 파일 로그");
> file.Run();
[1] 파일 로그 기능을 실행합니다.
> 
> var db = new WhitchService("[2] DB 로그");
> db.Run();
[2] DB 로그 기능을 실행합니다.

읽기 전용 필드는 클래스의 인스턴스 생성 시 넘겨온 값에 따라 한 번 선언 후 변경되지 않고 사용할 수 있는 기능을 만드는데 사용됩니다. 상수는 선언과 동시에 반드시 초기화해야 에러가 발생하지 않지만, 읽기 전용 필드는 선언과 동시에 초기화도 가능하고 선언 후 생성자에 의해 초기화도 가능합니다.

식 본문 생성자(Expression Bodied Constructor)

화살표 연산자를 사용하여 함수를 줄여서 표현하는 것처럼 생성자 코드를 줄여서 표현할 수 있습니다. 이것을 식 본문 멤버 중에서 식 본문 생성자라고 합니다.

코드: ExpressionBodiedConstructor.cs

> //[?] 식 본문 생성자(Expression Bodied Constructor) 
> class Pet
. {
.     private string _name;
.     // Expression Bodied Constructor
.     public Pet(string name) => _name = name;
.     public override string ToString()
.     {
.         return _name;
.     }
. }
> 
> var pet = new Pet("야옹이");
> pet.ToString()
"야옹이"

생성자도 앞서 배운 함수와 마찬가지로 화살표 연산자를 사용하여 축약해서 사용할 수 있습니다.

기본 생성자

C# 12.0에서는 기본 생성자(Primary Constructor) 라는 새로운 기능이 도입되었습니다. 이는 클래스의 생성자 선언을 보다 간결하게 만들어주며, 불필요한 코드 중복을 줄이고 가독성을 높여줍니다.

기본 생성자는 클래스 선언의 일부로 정의되며, 매개변수를 직접 클래스 본문에서 사용할 수 있습니다. 이를 통해 readonly 멤버 변수를 보다 쉽게 초기화할 수 있으며, 불필요한 필드 선언을 줄일 수 있습니다.

C# 12.0 이전에는 다음과 같이 필드를 선언한 후, 생성자에서 명시적으로 초기화해야 했습니다.

class Employee
{
    private readonly string _name;
    private readonly int _age;

    public Employee(string name, int age)
    {
        _name = name;
        _age = age;
    }
}

C# 12.0의 기본 생성자를 사용하면 더 간결하게 표현할 수 있습니다.

class Employee(string name, int age)
{
    private readonly string _name = name;
    private readonly int _age = age;
}

이렇게 하면 생성자 선언과 필드 초기화를 한 줄로 해결할 수 있습니다.

다음은 기본 생성자를 활용한 예제입니다.

코드: PrimaryConstructorDemo.cs

using System;

class Employee(string name, int age) // 기본 생성자 사용
{
    private readonly string _name = name;
    private readonly int _age = age;

    public void Display() => 
        Console.WriteLine($"이름: {_name}, 나이: {_age}");
}

class PrimaryConstructorDemo
{
    static void Main()
    {
        Employee employee = new("홍길동", 21);
        employee.Display();
    }
}
이름: 홍길동, 나이: 21

C# 12.0의 기본 생성자는 필드 선언과 초기화를 한 줄로 처리하여 코드의 간결성을 높이고, 불필요한 필드를 제거하여 매개변수를 직접 활용할 수 있으며, 클래스의 생성자와 초기화 과정을 명확하게 보여줌으로써 가독성을 향상시키는 장점을 제공합니다.

장 요약

생성자를 사용하면 클래스의 기본값을 설정하고 인스턴스화되는 개체에 대한 제약을 둘 수 있고 읽기 쉬운 코드를 작성할 수 있습니다. 필드와 같이 private 액세스 한정자를 같는 멤버들은 생성자를 통해서 초기화해서 사용이 가능합니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com