장난감 연구소

[C#] public 필드 vs 자동 구현 프로퍼티 비교 분석 본문

프로그래밍/C#

[C#] public 필드 vs 자동 구현 프로퍼티 비교 분석

changi1122 2020. 2. 9. 17:00
    728x90

    이 글은 C#에서 public 필드(Field)자동 구현 프로퍼티(Auto-Implemented Property) 중 무엇을 사용해야 하는가 고민에서 시작하여, 필드와 자동 구현 프로퍼티의 차이점, 성능에 관해 실험해본 내용을 다룬다.

    부족한 부분이 많아 계속 수정해나가고 있고, 그다지 유익한 내용을 담고 있는 글은 아닙니다.

     

    필드와 자동 구현 프로퍼티

    객체지향 프로그래밍 패러다임에서는 정보 은닉을 위해 private 변수를 선언하고, 이 변수를 읽고 쓰는 get, set 메서드를 만들기도 한다. 아래 코드처럼 말이다. (참고로 이 get, set은 각각 getter, setter 또는 접근자(accessor), 설정자(mutator)라고 불린다.)

    class Gun
    {
        private int bullet;
        public int GetBullet() {
            return this.bullet;
        }
        public void SetBullet(int value) {
            this.bullet = value;
        }
    }

    그런데 이런 방식으로 매우 큰 클래스를 만들게 되면 문제가 발생한다. 그 클래스 내에는 많은 필드와 그 필드를 읽고, 쓰기 위한 get, set 메서드가 무수히 많아진다. 그렇게 되면 프로그래머들이 특정 필드를 찾는 것조차 어려워진다.

    그래서 C#에서는 이를 간단하게 get, set 키워드로 작성하는 프로퍼티(Property)를 제공한다. 또, 이를 더 단순화하여 private 필드를 자동으로 만들어주는 자동 구현 프로퍼티도 제공한다. 아래에 프로퍼티를 활용해 위쪽 Gun 클래스 다시 만들어 보았다.

    class Gun
    {
        private int bullet;
        public int bullet {
            set { bullet = value; }
            get { return bullet; }
        }
    }
    
    //자동 구현 프로퍼티
    class Gun
    {
        public int bullet { get; set; }
    }

     

    프로퍼티 사용 이유

    객체지향 프로그래밍에서 정보 은닉을 지향하는 이유라 하면, 객체 내부의 데이터를 외부에 보이게 하지 않음으로써 클래스의 구체적인 내부 구현을 모르고도, 어떤 메서드를 사용해 클래스를 사용할 수 있게 한다는 것이다. 예를 들어 스택을 구현한 클래스에서 private으로 선언된 size 변수는 리스트의 길이를 담고 있겠지만, 외부에서 push, pop 메서드로 스택을 사용할 때 알 필요가 없다.

    get, set 메서드를 사용하는 이유는 특정한 방법이나 제한적으로 private 필드에 접근할 수 있도록 하기 위해서이다. ①스택의 size와 같이 외부에서 건드리면 안 되는 값은 get 메서드만 가지게 해 외부에서 변경할 수 없도록 제한하거나, ②며칠인지 저장해주는 필드는 set 메서드에서 1~31일까지의 값만 받도록 해 대입하기 전에 검증할 수도 있다. 또, ③특정한 값을 계산해서 반환해줄 수 있을 뿐더러 ④나중에 외부에 영향없이 클래스를 수정하기 쉽다.

    아래에 ①, ②, ③을 반영하여 만든 손목시계 클래스처럼 말이다. (참고로 여기서 생각한 손목시계는 시간은 한번 맞추면 안 맞춰도 되지만, 오늘이 며칠인지는 때때로 직접 맞춰야 하는 시계이다.)

    class watch
    {
        public int hour { get; }  //시간(1)
        public int minute {get; } //분(1)
        public int day //며칠(2)
        {
            get { return day; }
            set { if(1 <= value && value <= 31) day = value; }
        }
        public bool isPM // 오후 여부(3)
        {
        	get { return ((hour < 12) ? false : true); }
        }
    }

     

     

    public 필드 vs 자동 구현 프로퍼티

    내가 이 글의 내용을 다루게 된 이유는, 값을 넣을 때 처리가 필요한 것도 아니면서 get 메서드 set 메서드 모두 필요한 필드라면, 단순한 public 필드를 사용하면 되는가, 프로퍼티를 만들어야 하는지 궁금하게 된 것이다. 아래 코드처럼 큰 차이도 없다.

    class MyClass //public 필드
    {
        public int number;
    }
    
    class MyClass //자동 구현 프로퍼티
    {
        public int number { get; set; }
    }

     

    빌드한 후 디컴파일 해보기

    위의 MyClass로 만들어진 객체의 number에 대입하고 사용하는데 차이가 없다. get 메서드, set 메서드로 필드에 접근하는 게 무의미하기 때문에 get, set 메서드가 생략되고 두 방법으로 만들어진 프로그램은 똑같지 않을까?

    ILSpy라는 프로그램을 이용하면 컴파일된 닷넷 프로그램을 기계어와 비슷한 역할을 하는 CIL로 되돌릴 수 있다. 기계어가 아니라 CIL인 이유는 닷넷 프로그램은 컴파일하면 CIL이란 중간 언어로 저장하고, 실행할 때 중간 언어를 번역하기 때문이다.

    간단하게 아래와 같은 클래스를 만들어 컴파일하고, 이를 다시 ILSpy로 디컴파일해 보았다.

    class Gun //public 필드
    {
        public int Bullet;
        public void Fire() {
            Bullet--;
        }
    }
    
    class Gun //자동 구현 프로퍼티
    {
        public int Bullet { get; set; }
        public void Fire() {
            Bullet--;
        }
    }

     

    public 필드를 사용한 클래스
    프로퍼티를 사용한 클래스

    결과부터 말하자면 자동 구현 프로퍼티를 사용했을 때는 '<Bullet>k__BackingField'라는 private 필드가 따로 생기고, get_Bullet (), set_Bullet (int32 value)라는 메서드도 생겼다. 두 방법으로 프로그램을 만들었을 때 프로그램이 다른 방법으로 작동한다는 것이다.

     

    두 방법의 성능 측정

    BenchmarkDotNet을 사용하여 두 방법의 성능을 측정해보았다. 위의 Gun 클래스를 활용하여 Bullet을 3으로 설정하고, Fire()를 3회 호출하는 과정을 100만 번 반복하였다.

    단순 필드를 사용하는 클래스에서는 2.229 ms의 시간이 소요되었고, 프로퍼티를 사용하는 클래스에서는 4.161ms의 시간이 소요되었다. 프로퍼티를 사용하였을 때가 80% 정도 더 많은 시간이 소요되었다. 이를 볼 때 특별한 이유 없이 get, set 메서드를 통해 접근하는 것이 무의미하다고 주장할 수도 있을 것이다. 하지만 100만 번 작업에서 약 2ms만큼 차이 난 것이기에, 그보다 훨씬 많은 작업이 프로그램에서 일어나는 것이 아니라면, 유의미하다고 할 만큼 큰 성능 차이는 없는 거로 보인다.

    두 방법의 차이점

    사실 두 방법 중 어떤 걸 사용하는 것이 좋은지 물어본 사람이 나 이외에도 있었다. Stack Overflow에 이에 관한 질문이 여럿 있었다. 그 답변 중 하나를 가져오면 다음과 같다.

    Properties can be used for data binding easily in most .NET UI frameworks.
    Reflection works differently.
    Differing access levels for get/set vs. for example your instance variable you can choose between readonly, private, protected, static, etc. as a whole.
    There is more overhead accessing a property. This is usually unimportant in most use cases other than games and highly performance sensitive situations.
    #링크

    차이점을 그대로 읊어 보자면, 프로퍼티는 데이터 바인딩을 할 수 있고, 외부에서 접근하는 것을 제한할 수 있다. 그렇지만 프로퍼티를 다루는 데 불필요한 과정이 있는 것도 맞다. 그래도 이는 성능에 민감한 상황 제외하고는 크게 중요하지는 않다고 한다. (Reflection 문장은 어떤 말인지 모르겠다.)

     

    결론

    결론 짓자면 멤버 변수를 만들 때, 외부에서 마음대로 값을 넣어야 하고, 값을 넣을 때 처리할 필요 없고, 그걸 데이터 바인딩할 필요도 없다면 단순히 public 필드로 변수를 선언하는 것이 성능에 유리하다. 그렇지만 대부분의 상황에서 프로퍼티를 사용했을 때와 유의미한 차이는 없다.

    값을 넣을 때 처리가 필요하거나, 데이터 바인딩할 것이라면 프로퍼티를 만들어야 한다. 다른 프로퍼티들과 일관성이 있는 소스 코드가 되는 것도 장점이다.

     

    참고자료

    C# Get Set vs Without Get Set [duplicate]

    Properties vs. Public Variables

    Changing a Field to a Property