C#LAB
C# 2.2 · 2부. 객체지향

필드, 프로퍼티, 접근 제한자

안에 담는 것과 밖에 내보이는 것을 구분합니다.

예상 학습 시간 15분 실행 단추가 있는 예제는 고쳐서 실행해 볼 수 있습니다 난이도 기초
개념 설명

안에 담는 것과 밖에 내보이는 것

앞 단원의 Student 는 값을 그대로 드러내고 있었습니다. 그러면 밖에서 아무 값이나 넣을 수 있습니다 — 점수에 -50을 담아도, 1000을 담아도 막을 길이 없습니다. 잘못된 값이 들어간 시점과 그것이 문제를 일으키는 시점이 멀리 떨어져 있어, 나중에 원인을 찾기도 어렵습니다.

그래서 값은 안에 감춰 두고, 드나드는 길만 밖에 냅니다. 그 길이 프로퍼티입니다.

  • 필드(field) — 값이 실제로 담기는 자리입니다. 대개 private 로 감춥니다.
  • 프로퍼티(property) — 그 값을 읽고 쓰는 길입니다. 겉보기는 필드와 같지만, 속에서는 검사하거나 다듬을 수 있습니다.
  • 접근 제한자 — 어디까지 보일지 정합니다 (public·private 등).
최소 예제 · 편집 가능

드나드는 길에 검사 두기

아래 코드는 브라우저 안에서 실제로 실행됩니다. 고쳐서 눌러 보셔도 됩니다.

Program.cs
var s = new Student();

s.Score = 90;
Console.WriteLine(s.Score);

s.Score = -50;      // 담기지 않고 0 이 됩니다
Console.WriteLine(s.Score);

class Student {
    private int _score;

    public int Score {
        get { return _score; }
        set { _score = value < 0 ? 0 : value; }
    }
}
출력
90 0

코드는 고쳐서 실행해 볼 수 있습니다. 처음 누를 때만 실행기를 내려받느라 잠시 걸립니다. 적은 코드는 서버로 나가지 않습니다.

밖에서는 s.Score = -50; 이라고 평범하게 담았는데 결과가 0입니다. 담는 길에 검사가 놓여 있기 때문입니다. 이것이 필드를 그대로 드러내는 것과의 차이입니다.

set 안의 value밖에서 넘어온 값을 가리키는 이름입니다. 우리가 선언하지 않아도 그 자리에 준비되어 있습니다.

상세 사용법

검사할 것이 없다면 한 줄로

드나들기만 하고 검사할 것이 없는 값이 대부분입니다. 그때마다 필드와 get·set 을 손으로 적는 것은 군더더기라, 중괄호 안에 get; set; 만 적으면 컴파일러가 숨은 필드를 만들어 이어 줍니다. 이것을 자동 구현 프로퍼티라고 합니다.

검사가 필요할 때
private int _score;

public int Score {
    get { return _score; }
    set {
        if (value < 0) value = 0;
        _score = value;
    }
}
그럴 것이 없을 때
public string Name { get; set; }

// 숨은 필드는 컴파일러가
// 만들어 이어 줍니다

둘은 밖에서 똑같이 보입니다. 그래서 처음에는 한 줄로 두었다가 검사가 필요해지면 그때 풀어 적으면 되고, 사용하는 쪽 코드는 바꾸지 않아도 됩니다. 필드를 그대로 드러냈다면 그럴 수 없습니다 — 나중에 검사를 넣으려면 그 필드를 쓰던 모든 곳을 고쳐야 합니다. 처음부터 프로퍼티로 두는 까닭이 여기 있습니다.

C# 14.0부터는 검사 한 줄 때문에 전체를 풀어 적지 않아도 됩니다. 자동 구현 프로퍼티가 만들어 두는 숨은 필드를 field 라는 이름으로 곧바로 부를 수 있게 되었습니다.

// 필드를 손으로 선언하지 않았습니다
public int Score {
    get;
    set => field = value < 0 ? 0 : value;
}

앞의 왼쪽 코드와 하는 일이 같지만 필드 선언이 사라졌습니다. 다만 이것은 .NET 10 이상에서만 되므로, 그보다 낮은 버전을 사용하는 곳에서는 여전히 필드를 손으로 선언해야 합니다.

어디까지 보일지 정하기

제한자보이는 범위주로 사용하는 곳
public어디서나프로퍼티 · 메서드
private그 클래스 안에서만필드
protected그 클래스와 물려받은 클래스2.4에서 다룹니다
internal같은 프로젝트 안밖에 낼 것이 아닌 형식

아무것도 적지 않으면 클래스 멤버는 private 입니다. 그래도 적어 두는 편이 낫습니다 — 감춘 것이 뜻한 바인지, 적기를 빠뜨린 것인지 읽는 사람이 알 수 있기 때문입니다.

필드 이름 앞에 밑줄을 붙이는 것(_score)은 문법이 아니라 널리 쓰이는 관례입니다. 프로퍼티 Score 와 필드 _score 를 한눈에 구분하려는 것입니다.

읽기만 하게 두기

밖에서 바꾸면 안 되는 값도 있습니다. set 을 아예 빼거나, 그쪽에만 private 을 붙이면 됩니다.

class Account {
    // 밖에서는 읽기만 — 안에서는 바꿀 수 있습니다
    public int Balance { get; private set; }

    public void Deposit(int amount) {
        if (amount > 0) Balance += amount;
    }
}

이렇게 두면 잔액을 바꾸는 길이 Deposit 하나로 좁아집니다. 지켜야 할 규칙을 한 자리에 모을 수 있다는 것이 값을 감추는 진짜 이득입니다.

만들 때만 담고 그 뒤로는 못 바꾸게 하려면 set 대신 init 을 적습니다(C# 9.0+).

public string Name { get; init; }

var s = new Student { Name = "홍길동" };   // 여기서는 됩니다
s.Name = "김철수";                          // 컴파일 오류
버전 배지

이 문법, 몇 버전부터 사용할 수 있나요

1.0
2.0
3.0
6.0
8.0
9.0
11.0
12.0
14.0
  • 1.0필드 · 프로퍼티 · 접근 제한자
  • 3.0자동 구현 프로퍼티 — { get; set; }
  • 6.0프로퍼티 기본값 · 읽기 전용 자동 프로퍼티
  • 9.0init — 만들 때만 담기
  • 14.0field — 숨은 필드를 이름으로 부르기
C# 3.0+

프로퍼티는 C# 1.0부터 있었지만, 한 줄로 적는 { get; set; } 은 C# 3.0부터입니다. 그 전에는 검사할 것이 없어도 필드와 get·set 을 모두 손으로 적어야 했습니다. C# 14.0의 field 키워드는 그 반대편을 메웁니다 — 검사 한 줄을 넣으려고 전체를 풀어 적던 일을 없앱니다.

실습 문제

직접 해보기

1. 나이를 0 아래로 못 내려가게 난이도 하

Person 클래스에 이름과 나이를 두되, 나이는 음수가 들어오면 0으로 담기게 해보세요. 이름은 검사할 것이 없습니다.

검사가 필요한 쪽만 풀어 적고, 나머지는 { get; set; } 한 줄로 둡니다. 넘어온 값의 이름은 value 입니다.
var p = new Person(); p.Name = "홍길동"; p.Age = -5; Console.WriteLine($"{p.Name} {p.Age}살"); // 홍길동 0살 class Person { private int _age; public string Name { get; set; } public int Age { get { return _age; } set { _age = value < 0 ? 0 : value; } } }
2. 밖에서 못 바꾸는 잔액 난이도 중

Wallet 클래스를 만들어 잔액은 밖에서 읽기만 되게 하고, 넣기와 빼기는 메서드로만 하게 해보세요. 잔액보다 많이 빼려 하면 아무 일도 일어나지 않아야 합니다.

담는 쪽에만 private 을 붙이면 밖에서는 읽기만 됩니다. 빼기 전에 잔액이 넉넉한지 보는 것은 1.5의 if 로 충분합니다.
var w = new Wallet(); w.Deposit(10000); w.Withdraw(30000); // 모자라서 아무 일도 없습니다 w.Withdraw(3000); Console.WriteLine(w.Balance); // 7000 class Wallet { public int Balance { get; private set; } public void Deposit(int amount) { if (amount > 0) Balance += amount; } public void Withdraw(int amount) { if (amount > 0 && amount <= Balance) Balance -= amount; } }
요약
  • 값은 필드에 감추고 드나드는 길만 프로퍼티로 냅니다.
  • 검사할 것이 없으면 { get; set; } 한 줄이면 됩니다. 밖에서는 똑같이 보입니다.
  • 담는 쪽에 private 을 붙이면 밖에서는 읽기만 됩니다.
  • 값을 감추는 이득은 지켜야 할 규칙을 한 자리에 모을 수 있다는 것입니다.

C# 버전별 변경 이력 에서 각 버전이 무엇을 더했는지 볼 수 있습니다.