Properties

Posted by Eun JongHyeok on December 12, 2022
  1. Accessor(접근자)
    1. get과 set
    2. Restricting Accessor Accessibility
  2. 자동 구현 프로퍼티
    1. 변경할 수 없는 프로퍼티
  3. 참고

저번 포스팅에서는 프로퍼티가 무엇인지에 대해 설명했었습니다. 이번 포스팅에서는 예제를 통해 프로퍼티의 형태가 어떻게 되고 어떻게 써야할지 알아보겠습니다.

참고로 유니티에서 제공하지 않는 Init Only Setters(C# 9.0), required(C# 11.0)에 대해선 생략하도록 하겠습니다. 또한 프로퍼티 사용법 중 상속과 다형성에 대한 부분들이 있는데 메서드와 유사하므로 나중에 따로 다루도록 하겠습니다.

Accessor(접근자)

get과 set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person
{
    // 백업 필드
        private int _age;
    // 프로퍼티
        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
}

public class Program
{
        static void Main(string[] args)
        {
            Person person = new Person();
      // set 접근자가 호출
            person.Age = 10;
      // get 접근자가 호출
            Console.WriteLine(string.Format("Age is {0}.", person.Age));
        }
}

기본 예제를 통해 get과 set에 대해 알아봅시다. 프로퍼티의 모습은 필드와 메서드 두 개의 모습을 같이하고 있습니다. 메서드처럼 블락이 있고 외부에서는 필드처럼 접근해 사용할 수 있습니다. 하지만 프로퍼티는 변수가 아니므로 ref나 out 매개변수로 사용할 수 없으니 주의해줍시다.

또한 프로퍼티 블럭 안에는 get과 set이라는 접근자가 있는 것을 확인할 수 있습니다.

get은 백업 필드를 이용하여 프로퍼티의 타입을 반환해주는 메소드처럼 되어있고 사용할 때는 필드처럼 사용해주면 됩니다. get에서 주의할 점은 get 내부에서 백업 필드의 값을 변경하는 것은 올바른 사용법이 아니라는 것입니다.

set도 마치 리턴 타입이 void은 메서드처럼 생겼습니다. set 내부에 value라는 변수가 들어 있는데 이는 외부에서 대입 연산자를 통해 set 접근자를 호출하여 들어온 암시적 매개변수 값입니다.

프로퍼티의 주요 용도 중에는 데이터의 유효성을 검사하는 용도가 있습니다. 위 예제에서는 Age를 통해 그대로 _age에 전달하고 있지만 아래 예제처럼 바꾸면 음수에 대해 예외로 내보낼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person
{
    private int _age;
    public int Age
    {
        get { return _age; }
        set {
            if (value >= 0)
                _age = value;
            else
                throw new ArgumentOutOfRangeException();
        }
    }
}

Restricting Accessor Accessibility

get과 set의 액세스 가능성은 디폴트값으로 프로퍼티의 액세스 가능성을 따라가지만 get과 set에 직접 액세스 한정자를 줄 수도 있습니다. 일반적으로 set에 더 제한적인 private이나 protected를 주는 경우가 많습니다.

제한 사항

  • 인터페이스에서 X
  • get과 set 접근자가 모두 포함된 경우에만 사용 가능, 둘 중 하나에만 적용 가능
  • override 한정자가 있으면 오버라이드한 프로퍼티와 같은 액세스 레벨로 선언해야함
  • 프로퍼티의 기본 값보다 더 제한적으로만 사용가능

예제를 통해 확인해보면 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface ISomeInterface
{
    int TestProperty
    {
        // 인터페이스에서는 접근제한자를 줄 수 없음
        get;
    }
}

public class TestClass : ISomeInterface
{
    public int TestProperty
    {
        // 인터페이스와 같은 레벨만 가능
        get { return 10; }

        // 인터페이스에서 아무런 접근 제한자를 주지 않았으니
        // public 보다 낮은 protected나 private 접근 제한자를 줄 수 있음
        protected set { }
    }
}

재밌는 점은 override가 아닌 new에서의 예제에서 확인할 수 있습니다.

set에 액세스 제한자를 줄 경우에는 에러가 나지만 프로퍼티에 private을 줄 경우 BaseClass의 프로퍼티가 호출되는 것을 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class BaseClass
{
    private string _name = "Name-BaseClass";
    private string _id = "ID-BaseClass";

    public string Name
    {
        get { return _name; }
        set { }
    }

    public string Id
    {
        get { return _id; }
        set { }
    }
}

public class DerivedClass : BaseClass
{
    private string _name = "Name-DerivedClass";
    private string _id = "ID-DerivedClass";

    new public string Name
    {
        get
        {
            return _name;
        }

        // override와 다르게 protected를 줄 수 있지만 이 경우
        // 메인함수에서 접근 못함 + 에러 표시
        set
        {
            _name = value;
        }
    }

    // 프로퍼티 자체에 private을 사용할 경우 
    // 외부에서 호출할 경우 Base 클래스의 Id가 호출됨
    new private string Id
    {
        get
        {
            return _id;
        }
        set
        {
            _id = value;
        }
    }
}

class MainClass
{
    static void Main()
    {
        BaseClass b1 = new BaseClass();
        DerivedClass d1 = new DerivedClass();

        b1.Name = "Mary";   // BaseClass Name프로퍼티에 set이 없으므로 아무 동작 X
        d1.Name = "John";

        b1.Id = "Mary123";
        d1.Id = "John123";  // BaseClass의 Id프로퍼티가 호출됨. 물론 set에 아무것도 없어 아무동작 X

        System.Console.WriteLine("Base: {0}, {1}", b1.Name, b1.Id);
        System.Console.WriteLine("Derived: {0}, {1}", d1.Name, d1.Id);
    }
}

자동 구현 프로퍼티

프로퍼티에 별다른 논리가 필요없을 경우 백업 필드를 생략하고 몸통을 생략할 수 있습니다.

1
2
3
4
public class Person
{
    public int Age { get; set; }
}

자동 구현 프로퍼티는 사실 컴파일러가 자동으로 private 백업 필드를 만들어 줍니다. 따라서 인터페이스에서는 사용이 불가능합니다. 인터페이스에서는 필드를 가질 수 없기 때문입니다.

1
2
3
4
interface Person
{
    int Age { get; set; }
}

그런데 인터페이스를 이런식으로 사용 할 수 있습니다. 처음에는 사용할 수 없다하고 실제로는 아무런 에러도 안뜨는 것을 이해할 수 없었습니다. 다행이도 저만 이런 생각을 한건 아니더군요. stackoverflow에도 올라온 글이지만 해답은 단순했습니다.

자동 구현 프로퍼티가 몸통이 없는거지 몸통이 없는 프로퍼티가 자동 구현 프로퍼티는 아니다

interface에서의 몸통이 없는 프로퍼티는 abstract 입니다. 즉 자동 구현 프로퍼티가 아닙니다.

변경할 수 없는 프로퍼티

외부에서 변경할 수 없도록 완전 캡슐화된 프로퍼티를 만드는 방법에 대한 내용입니다.

  • get만 선언합니다. 이 경우 프로퍼티는 읽기 전용이 됩니다. 생성자에서만 접근 가능합니다.
  • set에 private으로 선언합니다. 이 경우 클래스 내부 함수에서만 변경이 가능합니다.

참고

Properties 문서


property
properties
Accessor

← Previous Post Next Post