본문 바로가기

C#

C#의 프로퍼티와 필드는 뭐가 다를까?

프로퍼티vs필드

프로그래밍을 입문하는 경우 C/C++로 입문하는 경우가 많다. 그래서 클래스를 "메서드"와 "멤버 변수"의 묶음으로 이해하는게 대부분이다. 그런데 C#에는 "멤버 변수" 대신 필드와 프로퍼티가 있다. 뭐가 다를까?

 

필드는 전통적인 의미의 멤버 변수이다. 인스턴스의 상태를 저장한다.

프로퍼티는 C++등에서도 흔하게 만들던 setter/getter 메서드를 언어 문법으로 가져온 것이다.

아래는 해당 개념을 코드로 표현한 것이다.

 

public class ClassWithProperty
{
    public int Field;

    private int _backingVariable;
    public int Property
    {
        get
        {
            return _backingVariable;
        }
        set
        {
            _backingVariable = value;
        }
    }
}

 

예전에는 아주 간단했다. 필드는 그냥 변수고, 프로퍼티는 setter/getter 메서드일 뿐이었다. 전혀 다른 개념으로 보였고 혼동할 여지는 적었다.

 

그런데 C# 3.0에서 자동으로 구현된 속성(Auto Implemented Property)이 등장하면서 헷갈리기 시작하게 된다.

public class ClassWithProperty
{
    public int Field;
    public int Property { get; set; }
}

 

문법이 이렇게 바뀌니 필드와 프로퍼티는 거의 동일하게 보인다. 

만약 특정 필드를 수정하면서 다른 연관된 작업, 예를 들면 값이 set 될때마다 로그를 찍는는 등의 일을 하고 싶다면 프로퍼티를 사용하면 간단할 것이다. 그런데 특별한 추가 작업 없이 단순히 값을 저장하고 불러오고 싶다면 어떻게 해야 할까?


언제 뭐를 쓰면 될까?

결론부터 짧게 말하면,

 

객체의 내부 상태만을 필드로 표현한다. 따라서 public 필드는 사용하지 않는다.

반대로, 객체의 외부에서 접근할 수 있는 정보는 전부 프로퍼티로 표현한다.

 

 

프로퍼티가 유연하다, 캡슐화에 유리하다는 등의 이론적인 이야기는 인터넷에 널린 다른 글에서 쉽게 찾아볼 수 있으니까 이 글에서는 좀 더 구체적인 예를 들어 보겠다.

 

  1. 프로퍼티는 인터페이스에 정의할 수 있다.
  2. 프로퍼티는 상속 및 오버라이드가 가능하다.
  3. 프로퍼티는 호출 시 메서드 호출에 해당하는 오버헤드가 있다.

 

실제 코딩에서 마주칠 차이점으로는 위에 나열한 정도가 있다.


readonly field vs getter-only property

C#에서 불변성을 생각하며 코딩하다 보면 변수를 불변으로 만들고 싶어질 때가 많이 있다.

 

C#의 readonly 키워드와 불변성에 대해

readonly vs const 처음 readonly를 접하면, 자연스럽게 const와 비교하게 된다. 언뜻 보기에는 둘다 변하지 않는 값을 선언하는 문법으로, 큰 차이를 느낄 수 없다. 다른 점을 찾아봐도 주로 const는 컴파

tearsinrain.tistory.com

 

그런데 readonly 필드와 getter만 존재하는 프로퍼티는 언뜻 보기에 굉장히 유사해 보인다. 둘다 생성자에서 값을 넣어줘야 하고, 한번 정해지면 값을 바꿀 수 없다.

 

진짜 그럴까? getter만 존재하는 프로퍼티의 경우 해당 프로퍼티가 인터페이스에 정의되어있거나, virtual로 지정되어 있을 경우 자식 클래스가 얼마든지 상속해서 getter의 구현을 바꿀 수 있다. 결과적으로 값도 바뀔 수 있다. 

 

public class Parent
{
    public virtual int Property { get; }
}

public class Child : Parent
{
    public override int Property => DateTime.Now.Second;
}

 

getter만 존재하는 프로퍼티의 값이 바뀔 수 있는 간단한 예이다. 그런데 만약 virtual이 아닌 프로퍼티여서 절대로 값이 바뀔 일이 없다면? 

 

다른 경우도 있다. 기능이 딱히 없이 데이터 저장 위주로 사용하는 클래스의 경우 굳이 번거롭게 프로퍼티를 달아줘야 할까? 

public class Person
{
    public readonly string Name;
    public readonly string Address;
}

 

이런 명백해보이는 것에까지 프로퍼티를 달아주는건 어쩐지 낭비처럼 느껴진다..


애매한 회색지대와 C#의 신기능들

위의 사례처럼 필드와 프로퍼티의 사용처가 혼동되는 경우를 완화하기 위해 C#에서는 여러 기능들을 꾸준히 추가해 오고 있다.

 

첫번째로 언급할 내용은 C# 9에 추가된 init 기능이다.

기존의 getter만 존재하는 프로퍼티 혹은 readonly 필드의 경우 생성자에서 값을 넣어줘야 했다. 그런데 값이 한두개면 괜찮지만 점점 늘어난다면 생성자를 만들고 매개변수를 늘리고 또 그 안에서 대입하는 코드를 일일이 작성하는게 너무 비생산적인 작업이 된다. 이러한 상용구boilerplate code를 줄이기 위해 속성에 init을 setter대신 정의하면 일반적인 객체 초기화 코드 안에서 딱 한번은 값을 지정할 수 있게 해준다.

 

var instance = new ClassWithProperty()
{
    Property1 = 1,
    Property2 = 2,
};

public class ClassWithProperty
{
    public int Property1 { get; init; }
    public int Property2 { get; init; }
}

 

 

두번째로, C# 7.2의 신기능인 readonly struct와 C# 9의 record 기능이 있다.

양쪽 다 불변하는 값의 묶음을 관리하기 위한 문법으로, 특히 record에는 불변 데이터를 다루기 위한 특수한 문법들이 몇개 더 추가되어 있다. 값을 다루려면 클래스에 필드를 정의하기보단 이들을 우선 고려해보는 것이 좋다.

 


하지만 가장 중요한 이유

마지막으로 필드와 프로퍼티를 이렇게 사용하는 가장 큰 이유가 남아있다. 그건 바로

 

남들이 다 그렇게 쓰기 때문

 

이다.

 

이상하게 들릴 수도 있지만 아주 중요한 문제이다. 일관된 사상에 따라 코드를 쓴다는 것은 곧 가독성과 유지보수성의 향상을 의미한다. 언제나 그렇듯이 소프트웨어는 쓸때는 한번 쓰지만 읽히는 경우는 수도 없이 많다. 따라서 이미 정립된 문화에 따라가는건 상상 이상으로 중대한 문제이다.

 

실제로 Entity Framework라던가 WinForm등의 마이크로소프트 프레임워크들은 대체로 위의 규칙을 따르고 있다. 예를 들면 Entity Framework에서 모델을 만들 때는 필드 대신 프로퍼티를 사용해야만 한다. 마이크로소프트 뿐만 아니라 많은 서드파티 라이브러리들도 해당 규칙을 따르고 있다.

 

 

유니티에서 테이블 데이터 로드하기 #2 - CSV

두번째로 소개할 형식은 CSV이다. 과거에는 비교적 사용 빈도가 높았으나, 최근에는 게임 등 일부 분야를 빼고는 사용 빈도가 많이 줄어든 느낌이다. 장점으로는 사람이 읽고 비교하기가 가장 쉬

tearsinrain.tistory.com

 

이 글에서 언급된 CsvHelper도, 필드를 인식하는 옵션을 제공하기는 하지만, 기본적으로 프로퍼티만 인식하도록 세팅되어 있다. 이렇듯이 해당 원칙을 지키면 외부 코드와의 상호작용도 더 쉬울 뿐더러 내 코드를 볼 다른 사람 (미래의 나 포함)에게 더욱 올바른 신호를 보내줄 수 있다.