생성자와 초기화
객체가 만들어질 때 무엇을 갖추어야 하는지 정합니다.
만들어질 때 갖추게 하기
지금까지는 객체를 만든 뒤에 값을 하나씩 담았습니다. 그러면 담기를 빠뜨려도 아무도 막지 않습니다. 이름 없는 학생, 값이 비어 있는 계좌가 만들어지고, 그것이 문제를 일으키는 것은 한참 뒤 엉뚱한 자리에서입니다.
생성자(constructor)는 객체가 만들어지는 그 순간에 실행되는 특별한 메서드입니다. 여기서 받을 것을 정해 두면 갖추지 않고는 만들 수 없게 됩니다.
- 이름이 클래스 이름과 같습니다.
- 반환 타입을 적지 않습니다 —
void도 적지 않습니다. new로 객체를 만들 때 저절로 불립니다.
받아야 만들어지는 클래스
아래 코드는 브라우저 안에서 실제로 실행됩니다. 고쳐서 눌러 보셔도 됩니다.
var a = new Student("홍길동", 90); var b = new Student("김철수", 85); Console.WriteLine(a.Describe()); Console.WriteLine(b.Describe()); class Student { public string Name { get; set; } public int Score { get; set; } // 이름이 클래스와 같고 반환 타입이 없습니다 public Student(string name, int score) { Name = name; Score = score; } public string Describe() => $"{Name}: {Score}점"; }
코드는 고쳐서 실행해 볼 수 있습니다. 처음 누를 때만 실행기를 내려받느라 잠시 걸립니다. 적은 코드는 서버로 나가지 않습니다.
new Student("홍길동", 90) 한 줄로 만들기와 갖추기가
함께 끝납니다. 그리고 이제 이름 없는 학생은 만들 수 없습니다 —
빠뜨리면 컴파일러가 막습니다.
만든 뒤에 담기와 만들면서 받기
var s = new Student(); s.Name = "홍길동"; // Score 를 빠뜨렸습니다 // 아무도 막지 않습니다 // 0점인지 안 적은 것인지 // 알 수 없습니다
var s = new Student("홍길동"); // 컴파일 오류 // — 점수를 넘기지 않았습니다 // 갖추지 않으면 // 만들어지지 않습니다
생성자를 만들면 사라지는 것
생성자를 하나라도 적으면 new Student() 가
컴파일되지 않습니다. 아무것도 적지 않았을 때만 컴파일러가 빈 생성자를
대신 만들어 주기 때문입니다. 우리가 하나를 적는 순간 그 호의는 사라집니다.
처음 클래스를 만들 때 거의 모두가 한 번은 마주치는 오류입니다. 빈 생성자도 함께 두고 싶다면 직접 적어 주면 됩니다.
class Student { public string Name { get; set; } public int Score { get; set; } public Student() { } // 빈 것도 두려면 적습니다 public Student(string name) : this(name, 0) { } // 아래 것에 넘깁니다 public Student(string name, int score) { Name = name; Score = score; } }
가운데 것의 : this(name, 0) 은 같은 클래스의
다른 생성자를 먼저 부르라는 뜻입니다. 담는 코드를 한 자리에 모아 두면
규칙이 바뀔 때 한 곳만 고치면 됩니다.
이름을 적어 담는 길
생성자에 넘기는 값이 여럿이면 new Student("홍길동", 90, 3, true)
처럼 되어 무엇이 무엇인지 알기 어려워집니다. 객체 초기화자를 사용하면
이름을 적어 담을 수 있습니다(C# 3.0+).
var s = new Student { Name = "홍길동", Score = 90 };
읽기는 좋지만 빠뜨려도 막지 못한다는 문제가 그대로 남습니다.
그래서 C# 11.0부터 required 가 생겼습니다. 붙여 두면
담지 않고는 만들 수 없습니다 — 초기화자의 읽기 좋음과 생성자의
강제함을 함께 얻습니다.
class Student { public required string Name { get; set; } public int Score { get; set; } } var a = new Student { Name = "홍길동" }; // 됩니다 var b = new Student { Score = 90 }; // 컴파일 오류 — Name 이 없습니다
받을 것을 이름 옆에 적기
밖에서 받은 것을 그대로 옮겨 담기만 하는 생성자가 대부분입니다. C# 12.0부터는 받을 것을 클래스 이름 옆에 적을 수 있고, 그 값을 클래스 안 어디서나 사용할 수 있습니다.
// 생성자를 따로 적지 않았습니다 class Student(string name, int score) { public string Name { get; } = name; public int Score { get; } = score; public string Describe() => $"{name}: {score}점"; }
어느 것을 쓸지는 무엇을 지키려는지에 달렸습니다. 반드시 갖춰야
할 것이 있으면 생성자나 required 를, 담을 것이 많고
대부분 선택이면 초기화자를 사용합니다.
이 문법, 몇 버전부터 사용할 수 있나요
- 1.0생성자 · 오버로딩 · this()
- 3.0객체 초기화자 — 이름을 적어 담기
- 11.0required — 담지 않으면 못 만들게
- 12.0기본 생성자 — 받을 것을 이름 옆에
생성자는 C# 1.0부터 있었습니다. 뒤에 더해진 것들은 모두 같은 물음을 다르게
풉니다 — 어떻게 하면 갖출 것을 갖추게 하면서도 짧게 적는가.
객체 초기화자는 읽기 좋게 했지만 강제하지 못했고,
required 가 그 구멍을 메웠으며, 기본 생성자는
옮겨 담기만 하던 코드를 없앴습니다.
직접 해보기
Book 클래스에 제목과 쪽수를 두되, 제목은
반드시 받아야 만들어지게 해보세요. 쪽수는 넘기지 않으면 0이면 됩니다.
같은 Book 을 이번에는 생성자 없이
만들되, 제목을 빠뜨리면 컴파일되지 않게 해보세요. 담을 때는 이름을 적어
담습니다.
- 생성자는 이름이 클래스와 같고 반환 타입이 없습니다.
- 생성자를 하나라도 적으면 매개 변수 없는 생성자는 사라집니다. 필요하면 직접 적습니다.
- 객체 초기화자는 읽기 좋지만 빠뜨림을 막지 못합니다.
required가 그것을 막습니다. - 옮겨 담기만 한다면 기본 생성자로 클래스 이름 옆에 적을 수 있습니다.
C# 버전별 변경 이력 에서 각 버전이 무엇을 더했는지 볼 수 있습니다.