프로퍼티

이정현
∙∙조회 7

20261012 프로퍼티

개요

필드를 private으로 닫아 두면 값을 넣고 꺼낼 통로가 따로 필요함. 지금까지는 메서드를 통해서 넣고 꺼내도록 하였음. 프로퍼티는 같은 통로를 필드처럼 쓰이는 문법으로 바꿔준다. 단순히 코드만 보면 필드에 식접 닿는 것 같지만, 실제로는 클래스가 정한 코드가 사이에서 돌아감. 그래서 값을 검사하거나, 읽기만 허용하거나, 저장하지 않고 계산해서 돌려주는 일이 가능해진다.

프로퍼티가 필요한 자리

필드를 그대로 여는 방법

가장 간단한 방법은 public이지만 아무 값이나 들어오기 때문에 잘못된 값이 들어올 수 있음.

메서드로 통로를 내는 방법

필드를 닫고 메서드를 두 개를 내면 값을 검사할 수 있음. 다만 쓰는쪽이 번거로워질 수 있음. 클래스 안의 값을 10 올리려고 할 때 player.SetHealth(player.GetHealth() + 10); 이렇게 될 수 있음.

프로퍼티 문법

get과 set 프로퍼티는 값을 읽을 때 실행되는 get 접근자와, 값을 쓸 때 실행되는 set접근자로 이루어짐. 값을 실제로 담아 두는 비공개 필드를 백킹 필드라고 부름.

csharp
// 프로퍼티 사용 예시
Person person = new Person();

person.Name = "홍길동";              // set 실행
Console.WriteLine(person.Name);     // get 실행

class Person
{
    private string name;           // 백킹 필드

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

set안의 value는 대입된 값이 담겨 오는 이름임. 선언한 적이 없어도 쓸 수 있고, 프로퍼티의 타입과 같은 타입임.

코드실행되는 것
person.Name = "홍길동";set본문, value에 "홍길동"
string s = person.Name;get본문, return한 값이s로
person.Name += "님";get실행 후 set실행

프로퍼티는 메서드 두개임

접근자 안에 출력을 넣어보면 언제 실행되는지 드러남.

csharp
// 참고 예시
class Probe
{
    private int value;

    public int Value
    {
        get
        {
            Console.WriteLine("  get 실행");
            return value;
        }
        set
        {
            Console.WriteLine($"  set 실행 (value = {value})");
            this.value = value;
        }
    }
}

위와 같은 상황에서 probe.Value += 5; 라고 했을 때 get한 번과 set한 번을 부름. 필드처럼 보이지만 실행되는 것은 메서드임.

  • 주의사항: get안에서 그 프로퍼티 자신을 쓰면 get이 자기 자신을 끝없이 부름. 백킹 필드 name을 써야 할 자리에 프로퍼티 이름 Name을 적는 실수가 대표적임. 컴파일은 되고, 실행할 때 메서드 호출이 쌓이는 공간인 스택이 넘쳐 프로그램이 멈춤.

프로퍼티 이름 규칙

종류규칙예
프로퍼티PascalCaseHealth, Name
백킹 필드camelCasehealth, name
프로퍼티는 PascalCase, 비공개 백킹 필드는 camelCase로 작성함.

자동 구현 프로퍼티

백킹 필드를 컴파일러에게 맡기기

검사할 것이 없고 그냥 담아 두기만 할 프로퍼티라면 본문 전체가 판박이가 됨. get;과 set;만 적으면 컴파일러가 백킹 필드를 대신 만들어 줌. 이것을 자동 구현 프로퍼티라고 함. 컴파일러가 만든 백킹 필드는 이름을 알 수 없고 클래스 안에서도 쓸 수 없음. 값에 닿는 길은 프로퍼티 이름뿐임.

csharp
// 자동 구현 프로퍼티 예시
class Developer
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Language { get; set; }
}

자동 프로퍼티 이셜라이저

자동 프로퍼티에도 선언 자리에서 기본값을 줄 수 있음.

csharp
// 자동 프로퍼티에 기본값 설정 예시
class Character
{
    public string Name { get; set; } = "이름 없음";
    public int Level { get; set; } = 1;
    public int MaxHealth { get; set; } = 100;
}

기본값을 주지 않으면 필드와 같은 규칙으로 채워짐(숫자는 0, bool은 false, 참조 타입은 null)

전체 프로퍼티와 자동 프로퍼티 고르기

두 방식을 한 클래스에 섞어 쓸 수 있음. 기준은 접근자 안에서 할 일이 있는가임.

상황고를 것
값을 담고 꺼내기만 함자동 프로퍼티
값을 검사하거나 가공해야 함전체 프로퍼티
저장하지 않고 계산해서 돌려줌get만 두는 프로퍼티

접근을 제한하는 프로퍼티

get만 두기. set을 아예 두지 않으면 바깥에서 읽을 수만 있음.

{get;} 형태의 자동 프로퍼티는 선언 자리와 생성자 안에서만 값을 넣을 수 있음.

private set

바깥에는 읽기만 열고 클래서 안에서는 계속 바꿔야 할 때 씀. 값을 바꾸는 길을 메서드로만 내는 것임.

csharp
// private set 예시
class Score
{
    public int Points { get; private set; }
    public int Combo { get; private set; }

    public void Add(int value)
    {
        Combo++;
        Points += value * Combo;
    }

    public void Miss()
    {
        Combo = 0;
    }
}

  • 주의사항: {get; private set;} 은 값이 바뀌긴 하되 바꾸는 경로를 클래스가 정한다는 뜻임. 정해진 뒤로 아예 바뀌지 않아야 하는 값이라면 readonly필드나 {get;} 쪽이 맞음.

set만 두기

반대로 get을 두지 않으면 넣을 수만 있고 꺼낼 수 없음. 드물게 쓰이지만 문법으로는 가능함.

csharp
// set만 사용한 프로퍼티
class Locker
{
    private string password;

    public string Password
    {
        set { password = value; }
    }

    public bool Check(string input)
    {
        return password == input;
    }
}

식 본문 프로퍼티

접근자 본문이 식 하나로 끝나면 =>로 줄일 수 있음.

csharp
// 식 본문 프로퍼티 예시
class Circle
{
    public double Radius { get; set; }

    public double Area => Math.PI * Radius * Radius;    //이렇게 하면 읽기만됨
    public double Circumference => 2 * Math.PI * Radius;//이렇게 하면 읽기만됨
}

위와 같이 프로퍼티 이름 뒤에 바로 =>를 쓰면 get만 있는 프로퍼티가 됨.get과 set을 모두 두면서 각각을 줄이려면 접근자마다 =>를 씀.

csharp
// get과 set 모두 가능한 식 본문 프로퍼티
class Counter
{
    private int count;

    public int Count
    {
        get => count;
        set => count = value;
    }

    public void Increase() => count++;
}

표기뜻
public int A => 식;get만 있는 프로퍼티
public int A { get => 식;}같음
public int A { get => 식; set => 문;}양쪽 모두
public int A { get; set;}자동 구현 프로퍼티

set에서 값을 검사하기

프로퍼티를 쓰는 가장 큰 이유. 잘못 된 값이 들어오는 자리를 한곳으로 모을 수 있음. 예로 player.Health -= 100;이 있다고 하면 결국 set을 거치므로 set에서 작성한 검사를 받음. 검사를 한 곳에만 두면 값을 바꾸는 모든 경로가 자동으로 그 검사를 통과하게 됨. 값을 다듬는 것 역시 마찬가지.

계산된 프로퍼티

값을 저장하지 않고, 부를 때마다 다른 값으로부터 만들어 돌려주는 프로퍼티임.

csharp
// 계산된 프로퍼티 예시
class Report
{
    public int Korean { get; set; }
    public int English { get; set; }
    public int Math { get; set; }

    public int Total => Korean + English + Math;
    public double Average => Total / 3.0;
    public bool IsPassed => Average >= 60;
}

Total, Average, IsPassed는 Korean, English, Math를 바꾸면 호출할 때마다 계산하기 때문에 바꾼 값으로 계산해서 반환함.

  • 주의사항: 계산된 프로퍼티는 부를 때마다 계산이 다시 돎. 반복문 안에서 무거운 계산을 하는 프로퍼티를 여러번 부르면 그만큼 비용이 늘어남. 그런 값은 한 번 꺼내 지역 변수에 담아두고 씀.

객체 이니셜라이저

프로퍼티가 여럿인 객체를 만들 때, 만드는 줄과 값을 채우는 줄이 흩어짐. new를 할 때 뒤에 중괄호를 붙여 프로퍼티를 채울 수 있음. 이것을 객체 이니셜라이저라고 함.

csharp
// 객체 이니셜라이저 예시
Course course = new Course { Id = 2, Title = "Unity 입문", Hours = 48 };
Console.WriteLine($"{course.Id}. {course.Title} ({course.Hours}시간)");

// 값이 많으면 아래처럼 줄을 나눠씀. 객체 배열을 만들 때 특히 짧아짐.
Course[] courses =
{
    new Course { Id = 1, Title = "C# 기초", Hours = 96 },
    new Course { Id = 2, Title = "C# 중급", Hours = 64 },
    new Course { Id = 3, Title = "Unity 입문", Hours = 48 }
};

class Course
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int Hours { get; set; }
}

위의 Course처럼 프로퍼티만 두고 데이터를 담는 데 쓰는 단순한 클래스를 POCO(Plain Old CLR Object)라고 부름. 객체 이니셜라이저는 생성자가 끝난 뒤에 실행됨. 생성자로 채운 값 위에 덮어쓰는 것임.

조건결과
채우려는 멤버가 public프로퍼티, 필드임불가
set이 없거나 private set임불가
생성자에 인수를 함께 넘김가능(new Item("...") {...})

set이 열려 있지 않은 프로퍼티는 객체 이니셜라이저로 채울 수 없음. 자동 프로퍼티만으로 이루어진 클래스는 데이터를 담아 나르는 용도로 자주 쓰임. 이런 클래스는 객체 이니셜라이저와 잘 맞음.

정적 프로퍼티

static을 붙이면 객체가 아니라 클래스에 하나만 있는 프로퍼티가 됨. 검사할 것이 있는 공유 설정에 씀. 정적 프로퍼티도 객체 없이 클래스 이름으로 부름. 백킹 필드 역시 static이어야 하며 이름은 접두사 없는 camelCase로 작성함.

인덱서

객체를 배열처럼 쓰기

클래스 안에 배열을 두고 그 배열을 다루게 하려면 메서드를 내야 함. 인덱서를 두면 객체 자체를 대괄호로 쓸 수 있음 인덱서는 이름 대신 this를 쓰고 대괄호 안에 매개변수를 두는 프로퍼티임.

kotlin
접근모디파이어 반환타입 this[인덱스타입 이름]
{
    get { ... }
    set { ... }
}

csharp
// 인덱서 사용 예시
Deck deck = new Deck(3);

deck[0] = "스페이드 A";
deck[1] = "하트 K";
deck[2] = "다이아 7";

for (int i = 0; i < deck.Count; i++)
{
    Console.WriteLine($"{i}번 카드: {deck[i]}");
}

class Deck
{
    private string[] cards;

    public Deck(int size)
    {
        cards = new string[size];
    }

    public int Count
    {
        get { return cards.Length; }
    }

    public string this[int index] // <- 인덱서 선언 this[] 를 사용
    {
        get { return cards[index]; }
        set { cards[index] = value; }
    }
}

인덱서 안에서도 get,set에 조건 검사를 실시 할 수 있음. 배열을 그대로 열어 두는 것과 달라지는 지점임.

문자열을 인덱스로 쓰기

인덱서의 매개변수는 정수가 아니어도 됨. 문자열을 받으면 이름으로 값을 찾는 형태가 됨.

csharp
// 문자열을 인덱스로 사용하는 예시
settings["해상도"] = "1920x1080";
settings["볼륨"] = "70";

class Settings
{
    private string[] keys;
    private string[] values;
    private int count;

    public Settings(int capacity)
    {
        keys = new string[capacity];
        values = new string[capacity];
    }

    public string this[string key]
    {
        get
        {
            for (int i = 0; i < count; i++)
            {
                if (keys[i] == key)
                {
                    return values[i];
                }
            }
            return "(설정 없음)";
        }
        set
        {
            for (int i = 0; i < count; i++)
            {
                if (keys[i] == key)
                {
                    values[i] = value;
                    return;
                }
            }

            if (count < keys.Length)
            {
                keys[count] = key;
                values[count] = value;
                count++;
            }
        }
    }
}

매개변수 타입이 다르면 인덱서를 어러 개 둘 수 있음. 이름이 모두 this이므로 구분은 대괄호 안의 타입으로 함.

인덱서와 프로퍼티의 차이

구분프로퍼티인덱서
이름직접 지음this 고정
쓰는 모양객체.이름객체[값]
매개변수없음있음(하나 이상)
여러 개 두기이름이 다르면 얼마든지매개변수 타입이 다르면 가능
static가능불가