프로퍼티는 필드 앞에 세운 창구다
앞에서 필드를 private 으로 막고 메서드로 열었습니다.
class Player
{
private int hp = 100;
public int GetHp() { return hp; }
public void SetHp(int value)
{
if (value < 0) value = 0;
hp = value;
}
}
안전해졌는데 쓰기가 번거로워졌습니다.
p.SetHp(p.GetHp() - 10); // 10 깎기
p.Hp -= 10 이라고 쓰고 싶습니다. 그런데 그러려면 필드를 열어야 하고,
열면 확인할 기회가 사라집니다.
밖에서는 필드처럼 보이고, 안에서는 코드가 실행되게 하면 둘 다 됩니다.
프로퍼티는 이렇게 생겼다
class Player
{
private int hp = 100;
public int Hp
{
get { return hp; }
set
{
if (value < 0) value = 0;
hp = value;
}
}
}
p.Hp = 50; // set 이 실행됩니다
Console.WriteLine(p.Hp); // get 이 실행됩니다
p.Hp -= 10; // get 하고 계산하고 set 합니다
겉보기는 필드, 실제로는 메서드입니다.
value 는 우리가 선언한 적이 없습니다. set 안에서만 쓸 수 있는 이름이고,
대입한 값이 여기 들어옵니다. p.Hp = 50 이면 value 가 50 입니다.
뒤에 숨어 있는 hp 를 백킹 필드라고 부릅니다. 실제로 값이 저장되는 자리입니다.
한쪽만 열 수 있다
읽기만 열고 쓰기는 막으려면 set 을 빼면 됩니다.
public int Hp
{
get { return hp; }
}
밖에서는 볼 수만 있고, 값을 바꾸는 것은 클래스 안의 메서드로만 합니다.
앞에서 GetHp 만 두고 SetHp 를 안 만들었던 그 방식입니다.
바깥에는 읽기만, 안에서는 쓰기도 열고 싶으면 set 에 접근 지정자를 붙입니다.
public int Hp
{
get { return hp; }
private set { hp = value; }
}
이제 클래스 안에서는 Hp = 50 이라고 쓸 수 있고 밖에서는 못 씁니다.
계산해서 돌려줘도 된다
프로퍼티가 반드시 필드 하나를 그대로 돌려줄 필요는 없습니다.
class Player
{
private int hp;
private int shield;
public int TotalHp
{
get { return hp + shield; }
}
public bool IsAlive
{
get { return hp > 0; }
}
}
TotalHp 라는 필드는 없습니다. 물어볼 때마다 계산합니다.
밖에서는 그냥 p.TotalHp 로 씁니다. 안에 필드가 몇 개인지 모릅니다.
앞에서 감추는 것은 값이 아니라 안에서 어떻게 하는지라고 했습니다. 이게 그 이야기입니다. 나중에 방어구를 추가해서 계산식이 바뀌어도 밖은 안 고칩니다.
읽기만 하는 프로퍼티는 화살표로 짧게 쓸 수 있습니다.
public int TotalHp => hp + shield;
public bool IsAlive => hp > 0;
자동 프로퍼티
확인할 것이 없는 필드도 있습니다. 그래도 나중을 위해 프로퍼티로 두고 싶은데, 그때마다 백킹 필드를 만드는 것이 번거롭습니다.
public string Name { get; set; }
중괄호 안에 get; 과 set; 만 적으면 컴파일러가 백킹 필드를 자동으로 만들어 줍니다.
우리 눈에는 안 보입니다.
지금은 public 필드와 하는 일이 같습니다. 그런데 다른 점이 있습니다.
나중에 확인이 필요해지면 여기만 고치면 됩니다.
private string name;
public string Name
{
get { return name; }
set
{
if (string.IsNullOrEmpty(value)) return;
name = value;
}
}
밖에서 p.Name 이라고 쓰던 코드는 한 글자도 안 바뀝니다.
public 필드였다면 이 변경이 밖에 전부 영향을 줍니다.
그래서 C# 에서는 public 필드를 거의 안 씁니다.
공개할 것은 자동 프로퍼티로 두는 것이 관습입니다. 지금 당장 얻는 것은 없지만
나중에 고칠 자유를 남겨 둡니다.
만들고 나면 못 바꾸게
set 대신 init 을 쓰면 객체를 만들 때만 넣을 수 있습니다.
class Point
{
public int X { get; init; }
public int Y { get; init; }
}
var p = new Point { X = 1, Y = 2 };
p.X = 99; // 컴파일 에러
중괄호로 값을 넣는 문법을 개체 초기화자라고 합니다. 생성자 없이도 채울 수 있습니다.
init 을 쓰면 그 자리에서만 되고 그다음부터는 못 고칩니다.
앞에서 해시 테이블을 볼 때 키로 쓴 값을 나중에 바꾸면 안 된다고 했습니다.
init 이 그걸 문법으로 막는 방법입니다.
메서드와 무엇으로 나누나
전부 프로퍼티로 만들면 안 됩니다. 기준이 있습니다.
| 프로퍼티 | 메서드 | |
|---|---|---|
| 무엇 | 상태를 읽고 쓴다 | 일을 시킨다 |
| 비용 | 싸야 한다 | 비싸도 된다 |
| 부작용 | 없어야 한다 | 있어도 된다 |
| 예 | Name, Count, IsAlive |
TakeDamage(), Save() |
p.Hp 라고 쓰는 사람은 그게 그냥 값을 읽는 것이라고 생각합니다.
거기서 파일을 읽거나 네트워크를 타면 안 됩니다.
괄호가 없으면 싸고 안전하다는 것이 읽는 사람의 기대입니다. 비싸거나 뭔가를 바꾸는 일은 메서드로 두고 괄호를 붙여야 합니다.
list.Count 는 프로퍼티고 list.Add(5) 는 메서드인 것이 그래서입니다.
정리
- 프로퍼티는 겉보기엔 필드지만 읽고 쓸 때 코드가 실행됩니다.
get은 읽을 때,set은 쓸 때 실행됩니다.set안의value가 대입한 값입니다.- 실제로 저장되는 필드를 백킹 필드라고 합니다.
set을 빼면 읽기만,private set이면 밖에서는 읽기만 됩니다.- 필드 없이 계산해서 돌려줘도 됩니다. 안이 어떻게 생겼는지 감춥니다.
{ get; set; }은 백킹 필드를 컴파일러가 만들어 주는 자동 프로퍼티입니다.public필드 대신 자동 프로퍼티를 씁니다. 나중에 고칠 자유를 남깁니다.init은 만들 때만 넣을 수 있게 합니다.- 상태를 읽고 쓰는 것은 프로퍼티, 일을 시키는 것은 메서드입니다.