C# 프로퍼티

김민구·2025년 5월 27일
0

C#

목록 보기
19/31

C# 프로퍼티

객체 지향 프로그래밍의 핵심 원칙 중 하나는 캡슐화 (Encapsulation)입니다. 이는 객체의 데이터를 외부에서 함부로 접근하거나 변경하지 못하도록 보호하는 것을 의미합니다. C#에서는 이러한 캡슐화를 우아하게 구현할 수 있는 강력한 기능인 프로퍼티 (Property)를 제공합니다.

이 글에서는 C# 프로퍼티가 무엇인지, 왜 사용해야 하는지, 그리고 다양한 형태의 프로퍼티 선언 및 사용 방법을 소스에 기반하여 자세히 살펴보겠습니다.

프로퍼티란 무엇인가?

프로퍼티는 클래스의 필드 (Field)에 접근하기 위한 특별한 멤버입니다. 마치 공개 필드처럼 보이지만, 실제로는 Get 접근자Set 접근자를 통해 필드의 값을 읽거나 쓰는 로직을 포함할 수 있습니다.

  • Get 접근자: 프로퍼티 값을 읽을 때 호출됩니다. 내부 필드 값을 반환하는 역할을 주로 합니다.
  • Set 접근자: 프로퍼티 값을 쓸 때 호출됩니다. value라는 암묵적인 매개변수를 사용하여 할당받은 값을 내부 필드에 저장하는 로직을 수행합니다.

과거에는 필드에 접근하기 위해 Get...()Set...()과 같은 메서드를 사용하는 방식이 일반적이었습니다 [3, 5 (상단)]. C# 프로퍼티는 이러한 Get/Set 메서드 쌍을 하나의 멤버로 묶어, 코드 가독성을 높이고 필드 접근과 유사한 자연스러운 문법을 제공합니다 [1, 5 (중단)].

예를 들어, private int myField;라는 필드가 있을 때, 이를 위한 Get/Set 메서드는 다음과 같습니다 [3, 5 (상단)].

public int GetMyField() { return myField; }
public void SetMyField(int newValue) { myField = newValue; }

이를 프로퍼티로 바꾸면 다음과 같이 간결해집니다 [5 (중단)].

public int MyField
{
    get { return myField; }
    set { myField = value; }
}

프로퍼티는 필드처럼 obj.MyField = 3; 또는 Console.WriteLine(obj.MyField);와 같이 사용할 수 있습니다 [5 (하단)].

자동 구현 프로퍼티 (Auto-Implemented Properties)

만약 Get 접근자에서 단순히 필드 값을 반환하고 Set 접근자에서 단순히 value를 필드에 할당하는 등 특별한 로직이 필요 없다면, 자동 구현 프로퍼티를 사용할 수 있습니다. 이 경우 Get/Set 접근자 본문을 생략하고 get; set;과 같이 선언합니다.

public string Name { get; set; }
public string PhoneNumber { get; set; }

컴파일러가 자동으로 비공개(private) 백킹 필드를 생성해줍니다. 이는 코드를 매우 간결하게 만들어 줍니다.

프로퍼티 초기화

객체를 생성함과 동시에 프로퍼티에 값을 할당하는 것을 객체 이니셜라이저 (Object Initializer)를 통해 수행할 수 있습니다.

ClassName instance = new ClassName()
{
    Property1 = value1, // 세미콜론(;)이 아닌 쉼표(,)입니다
    Property2 = value2,
    // ...
};

예시 코드를 보면 BirthdayInfo birth = new BirthdayInfo() { Name = "서현", Birthday = new DateTime(1991, 6, 28) };와 같이 객체 이니셜라이저를 사용하여 객체 생성 시 프로퍼티 값을 설정하고 있습니다.

init 전용 접근자

C# 9.0부터는 init 접근자를 사용할 수 있습니다. init 접근자는 set 접근자와 유사하게 값을 할당하지만, 오직 객체 초기화 시에만 값을 할당할 수 있습니다. 즉, 객체 생성 후에는 해당 프로퍼티의 값을 변경할 수 없습니다.

public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }

이렇게 선언된 프로퍼티는 생성자나 객체 이니셜라이저에서만 값을 설정할 수 있으며, 이후에 값을 변경하려 하면 컴파일 오류가 발생합니다. 이는 객체의 불변성 (Immutability)을 높이는 데 유용합니다.

required 키워드

C# 11부터는 required 키워드를 프로퍼티 앞에 붙여 해당 프로퍼티가 객체 생성 시 반드시 초기화되어야 함을 강제할 수 있습니다. required 프로퍼티는 생성자나 객체 이니셜라이저를 통해 초기화되지 않으면 컴파일 오류가 발생합니다.

public required string Name { get; set; }
public required DateTime Birthday { get; init; }

required 키워드는 특히 init 전용 프로퍼티와 함께 사용하여 객체가 생성될 때 필수적인 데이터가 누락되지 않도록 보장하는 데 효과적입니다.

레코드 형식 (Record Types)

레코드 형식 (Record Types)은 주로 데이터를 저장하는 객체를 위해 설계되었습니다. 레코드는 불변성을 기본으로 하며, 프로퍼티 선언을 간결하게 할 수 있는 문법을 제공합니다.

record RTransaction
{
    public string From { get; init; }
    public string To { get; init; }
    public int Amount { get; init; }
}

위 예시처럼 레코드 내에 프로퍼티를 선언하면 자동으로 init 전용 접근자가 생성됩니다.

레코드 형식은 객체를 복사할 때 with을 사용할 수 있습니다. with 식은 원본 레코드 객체의 모든 상태를 복사한 후, 지정된 프로퍼티의 값만 변경하여 새로운 레코드 객체를 생성합니다. 이는 원본 객체의 불변성을 유지하면서 데이터를 쉽게 변경할 수 있게 해줍니다.

RTransaction tr1 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
RTransaction tr2 = tr1 with { To = "Charlie" }; // tr1을 복사하고 To 프로퍼티만 변경하여 새 객체 tr2 생성

또한, 레코드 형식은 값 기반 동등성 비교를 기본으로 제공합니다. 두 레코드 객체의 모든 프로퍼티 값이 같으면 Equals 메서드나 == 연산자로 비교했을 때 true를 반환합니다. 이는 클래스가 기본적으로 참조 기반 동등성 비교를 수행하는 것과 대조됩니다.

무명 형식 (Anonymous Types)

무명 형식 (Anonymous Types)var 키워드와 함께 사용하여 컴파일러가 형식을 유추하게 하는 방식으로 객체를 생성할 때 사용됩니다.

var myInstance = new { Name = "박상현", Age = 17 };

무명 형식으로 생성된 객체는 선언 시 지정된 프로퍼티를 가지며, 이 프로퍼티들은 읽기 전용입니다. 무명 형식은 임시로 데이터를 묶어서 사용할 때 유용합니다.

인터페이스의 프로퍼티

인터페이스는 멤버의 선언만 가질 수 있으므로, 인터페이스에서 프로퍼티를 선언할 때는 Get 또는 Set 접근자만 명시하거나 둘 다 명시합니다.

interface IProduct
{
    string ProductName { get; set; } // get, set 접근자 모두 필요
}

interface INamedValue
{
    string Name { get; set; }
    string Value { get; set; }
}

인터페이스를 구현하는 클래스는 인터페이스에 선언된 모든 프로퍼티를 반드시 구현해야 합니다. 인터페이스 프로퍼티는 자동 구현 프로퍼티처럼 선언할 수 없습니다.

추상 클래스의 프로퍼티

추상 클래스에서도 프로퍼티를 선언할 수 있습니다. 추상 클래스는 구현이 있는 일반 프로퍼티와 추상 프로퍼티 모두를 가질 수 있습니다.

  • 일반 프로퍼티: 추상 클래스 내에서 Get/Set 접근자에 구현 코드를 가집니다. 파생 클래스에서 재정의할 수 있습니다.
  • 추상 프로퍼티: abstract 키워드를 사용하여 선언하며, Get/Set 접근자에 구현 코드가 없습니다. 추상 프로퍼티를 포함하는 추상 클래스를 상속받는 파생 클래스는 반드시 해당 추상 프로퍼티를 재정의 (Override)하여 구현해야 합니다.
abstract class Product
{
    private static int serial = 0;
    public string SerialID { get { return String.Format("{0:d5}", serial++); } } // 구현이 있는 일반 프로퍼티

    public abstract DateTime ProductDate { get; set; } // 구현이 없는 추상 프로퍼티
}

class MyProduct : Product
{
    public override DateTime ProductDate { get; set; } // 파생 클래스에서 반드시 구현 (재정의) 해야 함
}
profile
C#, Unity

0개의 댓글