1. 소개
C#에서 프로퍼티 없이 코딩할 수는 있지만, 그건 백악관에서 롤러스케이트 타는 거랑 비슷해 — 할 수는 있는데 진짜 불편하지. 이제 우리 다들 쓸데없는 getter/setter 없이 짧은 문법에 익숙해졌고, 앱이 좀 “진지한” 모델(예를 들어 시스템에서 유저를 설명하는 User 클래스 같은 거)을 다루기 시작하면, 꼭 필요한 데이터가 진짜로 다 들어있는지 보장하는 게 중요해져.
예를 들어, 이런 클래스가 있다고 해보자:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
프로퍼티 초기화 깜빡하기 진짜 쉬워:
User user = new User(); // Name은 null, Age = 0이야
결국 열 번쯤 화면 넘기고 로직 백 번 돌다 보면, 그 유명한 NullReferenceException 만나고 범인 찾느라 시간 다 버릴걸.
init-only 프로퍼티 기억나?
맞아, 생성할 때만 초기화하는 것도 쓸 수 있지:
public string Name { get; init; }
근데 이 문법 써도, 클래스 쓰는 사람이 값 안 넣으면 그만이야 — 대부분의 기본 생성자는 필드를 기본값(null, 0 등)으로만 초기화해.
그럼 어떻게 해야 프로그래머, 아니면 나 자신이라도 꼭 필요한 값을 안 까먹고 넣게 만들 수 있을까? 바로 그걸 위해 required modifier가 C# 11에 등장한 거야.
2. required 프로퍼티: 빡세게 초기화
required modifier는 “이 프로퍼티는 객체 만들 때 꼭 명시적으로 값이 들어가야 해!”라고 컴파일러한테 알려주는 거야. 쉽게 말해서, 객체 만들 때 이 프로퍼티 안 넣으면 컴파일러가 절대 넘어가지 않고, IDE도 무서운 빨간 밑줄 그어줄걸.
public class User
{
public required string Name { get; set; }
public int Age { get; set; }
}
유저 만들어볼까:
// 컴파일 에러: Name 프로퍼티는 꼭 초기화해야 해!
User user1 = new User();
아니면 이렇게:
// 컴파일 에러: required 프로퍼티 Name이 없음
User user2 = new User { Age = 18 };
이게 정답:
User user3 = new User { Name = "헤르미오나", Age = 18 };
required는 어떻게 동작해?
required modifier는 컴파일러한테 “객체 생성자(어떤 거든) 끝나고 나면, 이 프로퍼티는 꼭 값이 있어야 해”라고 알려주는 거야.
이건 일반 프로퍼티에도, init-only에도 다 적용돼:
public required string Name { get; init; }
직접 만든 생성자에서 required 프로퍼티 값 넣어주면 그것도 OK.
보통 이런 식으로 체크해
| 시나리오 | 컴파일러 OK? |
|---|---|
| required 프로퍼티 안 넣음 | ❌ |
| object initializer에서 넣음 | ✔️ |
| 생성자에서 넣음 | ✔️ |
우리 “강아지” 모델 예시
public class Dog
{
public required string Name { get; set; }
public int Age { get; set; }
}
Dog dog = new Dog { Name = "보빅", Age = 5 }; // 완전 OK!
Dog badDog = new Dog { Age = 2 }; // 에러! Name 안 넣음
required가 진짜 도움되는 곳?
- DTO 레이어 사이 전달: API 만들 때, 꼭 필요한 필드가 항상 들어오게 하고 싶을 때.
- 필수 속성 있는 복잡한 모델: 예를 들어 Product에 SKU, Order에 주문번호 등 꼭 있어야 하는 값.
- 면접이나 코드리뷰 때: 이런 문법 보여주면, 코드 읽는 사람이 존경(살짝 부러움도)할걸.
3. required랑 생성자 같이 쓰면?
가끔 생성자를 직접 쓸 때가 있지. 만약 생성자나 object initializer에서 required 프로퍼티 안 넣으면? 컴파일러가 에러 내.
public class Article
{
public required string Title { get; set; }
public required string Author { get; set; }
public Article()
{
// Title, Author 안 넣으면 컴파일 에러!
// 이렇게 하면 OK:
Title = "제목 없음";
Author = "알 수 없음";
}
}
생성자에서 required 프로퍼티 값 넣어주면 문제 없어. 안 그러면 object initializer(new Article { ... })로 꼭 넣어야 해.
쓸 때 주의할 점
- required는 프로퍼티에만 쓸 수 있고, 필드에는 안 돼.
- required는 상속 안 돼 — 부모에 required 있어도, 자식에서 안 쓰면 컴파일러는 뭐라 안 해(근데 명시적으로 자식에도 써주는 게 좋아).
- required는 자동 필드나 property 말고는 못 써.
4. 화살표(arrow) 프로퍼티 문법
C# 새 버전 나오면서 개발자들이 점점 더 간결하고 읽기 쉬운 코드를 좋아하게 됐지. 프로퍼티에서 제일 눈에 띄는 변화 중 하나가 화살표 문법(또는 expression-bodied properties)이야.
가끔 그냥 값만 반환하는 프로퍼티 만들고 싶을 때가 있잖아. 예전엔 꼭 중괄호로 getter 다 써야 했지:
public int Age
{
get { return birthYear > 0 ? DateTime.Now.Year - birthYear : 0; }
}
이제는 훨씬 짧게 — 화살표(=>)로 쓸 수 있어:
public int Age => birthYear > 0 ? DateTime.Now.Year - birthYear : 0;
이런 문법을 expression-bodied property라고 해. 간단한 계산에 딱이고, 코드도 훨씬 깔끔해져.
get이랑 set 같이 쓰는 예시
public class Book
{
private string _title;
public string Title
{
get => _title;
set => _title = value.Trim();
}
}
여기서 get은 필드 값 반환하고, set은 값 할당 전에 양쪽 공백을 잘라줘.
get만 쓰는(읽기 전용) 예시:
읽기 전용 프로퍼티면 중괄호 없이 그냥 =>로 쓸 수 있어.
public class Person
{
private string name = "마크 트웨인";
// 읽기 전용: 계산 프로퍼티
public string Name => name.ToUpper();
}
여기서 Name 프로퍼티는 읽기만 가능하고, 항상 name을 대문자로 반환해.
5. 프로퍼티에서 field 키워드
C# 14 전에는, 자동 프로퍼티의 숨겨진 필드에 setter/getter에서 직접 접근하고 싶어도 못 했어. 그래서 필드를 따로 선언해야 했지.
C# 14부터는 field 키워드로 자동 프로퍼티의 숨겨진 필드에 바로 접근할 수 있어:
public class Person
{
public string Name
{
get => field; // field는 Name 프로퍼티의 숨겨진 필드야
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("이름은 비어 있을 수 없어!");
field = value; // _name 대신 field 사용
}
}
}
예전엔 이렇게 써야 했지:
private string _name;
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("이름은 비어 있을 수 없어!");
_name = value;
}
}
변수 선언 하나 줄었지. 코드가 짧을수록 좋아.
왜 프로퍼티 안에서 필드에 직접 접근해야 할 때가 있을까?
- 가끔 값이 어디에, 어떻게 저장되는지 직접 컨트롤하고 싶을 때(예: 객체 복사, 캐싱, lazy-loading 등).
- attribute나 reflection 쓸 때 — 필드 이름이 필요할 수도 있어.
- (디)직렬화나 성능 튜닝할 때 값 저장을 더 세밀하게 다루고 싶을 때.
사용 예시:
setter에서 데이터 검증
public double Grade
{
get => field;
set
{
if (value < 0 || value > 5)
throw new ArgumentOutOfRangeException("점수는 0에서 5 사이여야 해");
field = value;
}
}
값 변경 통계
public int StepCount
{
get => field;
set
{
if (value > field)
{
Console.WriteLine($"우와! {value - field} 걸음 더 걸었어!");
}
field = value;
}
}
Lazy Load
public string Data
{
get
{
if (field == null)
field = LoadDataFromDatabase();
return field;
}
set => field = value;
}
6. 흔한 실수랑 팁
실수 1: required 프로퍼티 초기화 깜빡함.
컴파일러가 바로 에러 내주니까, 런타임에서 문제 생기는 거 미리 막아줘.
실수 2: 생성자에서 required 프로퍼티 전부 초기화 안 함.
생성자가 모든 필수 프로퍼티 값 안 받으면, 컴파일러가 뭐 빠졌다고 알려줘.
실수 3: const나 readonly랑 required 같이 쓰려고 함.
이 modifier들은 같이 못 써 — required는 일반 프로퍼티에만 가능해. 같이 쓰면 에러 나.
GO TO FULL VERSION