CodeGym /행동 /C# SELF /required 프로퍼티랑

required 프로퍼티랑 field 프로퍼티

C# SELF
레벨 17 , 레슨 3
사용 가능

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; }
}
required로 필수 프로퍼티 만들기

유저 만들어볼까:

// 컴파일 에러: 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: constreadonly랑 required 같이 쓰려고 함.
이 modifier들은 같이 못 써 — required는 일반 프로퍼티에만 가능해. 같이 쓰면 에러 나.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION