속성Properties

속성은 C#의 주요 구성 요소입니다.Properties are first class citizens in C#. 언어는 개발자가 디자인 의도를 정확하게 표현하는 코드를 작성할 수 있는 구문을 정의합니다.The language defines syntax that enables developers to write code that accurately expresses their design intent.

속성은 액세스 시 필드처럼 동작합니다.Properties behave like fields when they are accessed. 그러나 필드와 달리 속성은 속성에 액세스하거나 할당할 때 실행되는 문을 정의하는 접근자로 구현됩니다.However, unlike fields, properties are implemented with accessors that define the statements executed when a property is accessed or assigned.

속성 구문Property syntax

속성 구문은 필드에 대한 자연 확장입니다.The syntax for properties is a natural extension to fields. 필드는 스토리지 위치를 정의합니다.A field defines a storage location:

public class Person
{
    public string FirstName;
    // remaining implementation removed from listing
}

속성 정의에는 해당 속성의 값을 검색하고 할당하는 getset 접근자에 대한 선언이 포함됩니다.A property definition contains declarations for a get and set accessor that retrieves and assigns the value of that property:

public class Person
{
    public string FirstName { get; set; }

    // remaining implementation removed from listing
}

위에 표시된 구문은 자동 속성 구문입니다.The syntax shown above is the auto property syntax. 컴파일러는 속성을 백업하는 필드의 스토리지 위치를 생성합니다.The compiler generates the storage location for the field that backs up the property. 또한 컴파일러는 getset 접근자의 본문을 구현합니다.The compiler also implements the body of the get and set accessors.

때로는 해당 형식의 기본값이 아닌 값으로 속성을 초기화해야 합니다.Sometimes, you need to initialize a property to a value other than the default for its type. 이 작업을 위해 C#에서는 닫는 중괄호 뒤에 속성의 값을 설정합니다.C# enables that by setting a value after the closing brace for the property. FirstName 속성의 초기 값을 null 대신 빈 문자열로 설정할 수도 있습니다.You may prefer the initial value for the FirstName property to be the empty string rather than null. 아래와 같이 지정하면 됩니다.You would specify that as shown below:

public class Person
{
    public string FirstName { get; set; } = string.Empty;

    // remaining implementation removed from listing
}

이 아티클의 뒷부분에서 살펴보겠지만 특정 초기화는 읽기 전용 속성에 가장 유용합니다.Specific initialization is most useful for read-only properties, as you'll see later in this article.

아래 표시된 대로 스토리지를 직접 정의할 수도 있습니다.You can also define the storage yourself, as shown below:

public class Person
{
    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }
    private string firstName;
    // remaining implementation removed from listing
}

속성 구현이 단일 식인 경우 getter 또는 setter에 대해 식 본문 멤버를 사용할 수 있습니다.When a property implementation is a single expression, you can use expression-bodied members for the getter or setter:

public class Person
{
    public string FirstName
    {
        get => firstName;
        set => firstName = value;
    }
    private string firstName;
    // remaining implementation removed from listing
}

이 아티클 전체에서 해당하는 경우 이 간소화된 구문이 사용됩니다.This simplified syntax will be used where applicable throughout this article.

위에 표시된 속성 정의는 읽기/쓰기 속성입니다.The property definition shown above is a read-write property. set 접근자에서 value 키워드를 확인합니다.Notice the keyword value in the set accessor. set 접근자에는 항상 value라는 단일 매개 변수가 있습니다.The set accessor always has a single parameter named value. get 접근자는 속성 형식(이 예제에서는 string)으로 변환할 수 있는 값을 반환해야 합니다.The get accessor must return a value that is convertible to the type of the property (string in this example).

이것이 기본 구문입니다.That's the basics of the syntax. 다양한 디자인 구문을 지원하는 여러 가지 변환이 있습니다.There are many different variations that support a variety of different design idioms. 각 변환에 대한 구문 옵션을 살펴보고 알아봅니다.Let's explore, and learn the syntax options for each.

시나리오Scenarios

위의 예제에서는 가장 간단한 속성 정의 사례 중 하나인 유효성 검사 없는 읽기/쓰기 속성을 보여 주었습니다.The examples above showed one of the simplest cases of property definition: a read-write property with no validation. getset 접근자에서 원하는 코드를 작성하여 다양한 시나리오를 만들 수 있습니다.By writing the code you want in the get and set accessors, you can create many different scenarios.

유효성 검사Validation

set 접근자에서 코드를 작성하여 속성으로 표현된 값이 항상 유효한지 확인합니다.You can write code in the set accessor to ensure that the values represented by a property are always valid. 예를 들어 이름을 비워 두거나 공백일 수 없다는 Person 클래스에 대한 규칙이 있다고 가정합니다.For example, suppose one rule for the Person class is that the name cannot be blank or white space. 다음과 같이 작성할 수 있습니다.You would write that as follows:

public class Person
{
    public string FirstName
    {
        get => firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("First name must not be blank");
            firstName = value;
        }
    }
    private string firstName;
    // remaining implementation removed from listing
}

속성 setter 유효성 검사의 일부인 throw 식을 사용하여 앞의 예제를 간소화할 수 있습니다.The preceding example can be simplified by using athrow expression as part of the property setter validation:

public class Person
{
    public string FirstName
    {
        get => firstName;
        set => firstName = (!string.IsNullOrWhiteSpace(value)) ? value : throw new ArgumentException("First name must not be blank");
    }
    private string firstName;
    // remaining implementation removed from listing
}

위의 예제에서는 첫 번째 이름을 비워 두거나 공백일 수 없다는 규칙을 적용합니다.The example above enforces the rule that the first name must not be blank or white space. 개발자가 작성하는 경우If a developer writes

hero.FirstName = "";

해당 할당으로 인해 ArgumentException이 throw됩니다.That assignment throws an ArgumentException. 속성 set 접근자의 반환 형식이 void여야 하므로 예외를 throw하여 set 접근자에서 오류를 보고합니다.Because a property set accessor must have a void return type, you report errors in the set accessor by throwing an exception.

이 동일한 구문을 시나리오에서 필요한 항목으로 확장할 수 있습니다.You can extend this same syntax to anything needed in your scenario. 서로 다른 속성 간의 관계를 확인하거나 모든 외부 조건에 대해 유효성을 검사할 수 있습니다.You can check the relationships between different properties, or validate against any external conditions. 유효한 모든 C# 문을 속성 접근자에서 사용할 수 있습니다.Any valid C# statements are valid in a property accessor.

읽기 전용Read-only

이 시점까지 살펴본 모든 속성 정의는 공용 접근자를 사용한 읽기/쓰기 속성입니다.Up to this point, all the property definitions you have seen are read/write properties with public accessors. 속성에 유효한 유일한 액세스 가능성은 아닙니다.That's not the only valid accessibility for properties. 읽기 전용 속성을 만들거나 set 및 get 접근자에 대해 다른 액세스 가능성을 제공할 수 있습니다.You can create read-only properties, or give different accessibility to the set and get accessors. Person 클래스가 해당 클래스의 다른 메서드에서만 FirstName 속성의 값을 변경할 수 있도록 한다고 가정합니다.Suppose that your Person class should only enable changing the value of the FirstName property from other methods in that class. set 접근자에 public 대신 private 액세스 가능성을 제공할 수 있습니다.You could give the set accessor private accessibility instead of public:

public class Person
{
    public string FirstName { get; private set; }

    // remaining implementation removed from listing
}

이제 FirstName 속성을 모든 코드에서 액세스할 수 있지만 Person 클래스의 다른 코드에서만 할당할 수 있습니다.Now, the FirstName property can be accessed from any code, but it can only be assigned from other code in the Person class.

set 또는 get 접근자에 제한적인 액세스 한정자를 추가할 수 있습니다.You can add any restrictive access modifier to either the set or get accessors. 개별 접근자에 설정하는 액세스 한정자는 속성 정의의 액세스 한정자보다 더 제한적이어야 합니다.Any access modifier you place on the individual accessor must be more limited than the access modifier on the property definition. 위 내용은 FirstName 속성이 public이지만 set 접근자가 private이므로 유효합니다.The above is legal because the FirstName property is public, but the set accessor is private. public 접근자를 사용하여 private 속성을 선언할 수 없습니다.You could not declare a private property with a public accessor. 속성 선언을 protected, internal, protected internal 또는 private로 선언할 수도 있습니다.Property declarations can also be declared protected, internal, protected internal, or, even private.

get 접근자에 더 제한적인 한정자를 설정하는 것도 가능합니다.It is also legal to place the more restrictive modifier on the get accessor. 예를 들어 public 속성이 있지만 get 접근자를 private로 제한할 수 있습니다.For example, you could have a public property, but restrict the get accessor to private. 이 시나리오는 실제로 거의 수행되지 않습니다.That scenario is rarely done in practice.

생성자 또는 속성 이니셜라이저에서만 설정할 수 있도록 속성 수정을 제한할 수도 있습니다.You can also restrict modifications to a property so that it can only be set in a constructor or a property initializer. 다음과 같이 Person 클래스를 수정할 수 있습니다.You can modify the Person class so as follows:

public class Person
{
    public Person(string firstName) => this.FirstName = firstName;

    public string FirstName { get; }

    // remaining implementation removed from listing
}

이 기능은 읽기 전용 속성으로 노출되는 컬렉션을 초기화하는 데 주로 사용됩니다.This feature is most commonly used for initializing collections that are exposed as read-only properties:

public class Measurements
{
    public ICollection<DataPoint> points { get; } = new List<DataPoint>();
}

계산된 속성Computed properties

속성은 멤버 필드의 값을 반환할 필요가 없습니다.A property does not need to simply return the value of a member field. 계산된 값을 반환하는 속성을 만들 수 있습니다.You can create properties that return a computed value. Person 개체를 확장하여 이름과 성을 연결해서 계산된 전체 이름을 반환하겠습니다.Let's expand the Person object to return the full name, computed by concatenating the first and last names:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName { get { return $"{FirstName} {LastName}"; } }
}

위 예에서는 문자열 보간 기능을 사용하여 전체 이름에 대한 서식이 지정된 문자열을 만듭니다.The example above uses the string interpolation feature to create the formatted string for the full name.

계산된 FullName 속성을 만드는 보다 간결한 방법을 제공하는 식 본문 멤버를 사용할 수도 있습니다.You can also use an expression-bodied member, which provides a more succinct way to create the computed FullName property:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName => $"{FirstName} {LastName}";
}

식 본문 멤버람다 식 구문을 사용하여 단일 식이 포함된 메서드를 정의합니다.Expression-bodied members use the lambda expression syntax to define methods that contain a single expression. 여기서 해당 식은 person 개체의 전체 이름을 반환합니다.Here, that expression returns the full name for the person object.

캐시된 평가 속성Cached evaluated properties

계산된 속성의 개념과 스토리지를 혼합하고 캐시된 평가 속성을 만들 수 있습니다.You can mix the concept of a computed property with storage and create a cached evaluated property. 예를 들어 처음 액세스할 때만 문자열 형식이 지정되도록 FullName 속성을 업데이트할 수 있습니다.For example, you could update the FullName property so that the string formatting only happened the first time it was accessed:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    private string fullName;
    public string FullName
    {
        get
        {
            if (fullName == null)
                fullName = $"{FirstName} {LastName}";
            return fullName;
        }
    }
}

하지만 위의 코드에는 버그가 포함되어 있습니다.The above code contains a bug though. 코드가 FirstName 또는 LastName 속성의 값을 업데이트하는 경우 이전에 평가한 fullName 필드는 유효하지 않습니다.If code updates the value of either the FirstName or LastName property, the previously evaluated fullName field is invalid. fullName 필드가 다시 평가되도록 FirstNameLastName 속성의 set 접근자를 수정합니다.You modify the set accessors of the FirstName and LastName property so that the fullName field is calculated again:

public class Person
{
    private string firstName;
    public string FirstName
    {
        get => firstName;
        set
        {
            firstName = value;
            fullName = null;
        }
    }

    private string lastName;
    public string LastName
    {
        get => lastName;
        set
        {
            lastName = value;
            fullName = null;
        }
    }

    private string fullName;
    public string FullName
    {
        get
        {
            if (fullName == null)
                fullName = $"{FirstName} {LastName}";
            return fullName;
        }
    }
}

이 최종 버전은 필요한 경우에만 FullName 속성을 평가합니다.This final version evaluates the FullName property only when needed. 이전에 계산한 버전이 유효한 경우 해당 버전이 사용됩니다.If the previously calculated version is valid, it's used. 다른 상태 변경으로 인해 이전에 계산한 버전이 무효화된 경우 다시 계산됩니다.If another state change invalidates the previously calculated version, it will be recalculated. 이 클래스를 사용하는 개발자는 구현 세부 정보를 알 필요가 없습니다.Developers that use this class do not need to know the details of the implementation. 이러한 내부 변경 내용은 Person 개체의 사용에 영향을 주지 않습니다.None of these internal changes affect the use of the Person object. 이것이 속성을 사용하여 개체의 데이터 멤버를 노출하는 주요 이유입니다.That's the key reason for using Properties to expose data members of an object.

자동 구현 속성에 특성 연결Attaching attributes to auto-implemented properties

C# 7.3부터 필드 특성은 자동 구현된 속성의 컴파일러에서 생성된 지원 필드에 연결될 수 있습니다.Beginning with C# 7.3, field attributes can be attached to the compiler generated backing field in auto-implemented properties. 예를 들어 고유한 정수 Id 속성을 추가하는 Person 클래스에 대한 수정 버전을 사용합니다.For example, consider a revision to the Person class that adds a unique integer Id property. 자동 구현 속성을 사용하여 Id 속성을 작성하지만 디자인은 Id 속성을 유지하기 위해 호출하지 않습니다.You write theId property using an auto-implemented property, but your design does not call for persisting the Id property. NonSerializedAttribute는 속성이 아니라 필드에만 연결할 수 있습니다.The NonSerializedAttribute can only be attached to fields, not properties. 다음 예제와 같이 특성에 field: 지정자를 사용하여 Id 속성의 지원 필드에 NonSerializedAttribute를 연결할 수 있습니다.You can attach the NonSerializedAttribute to the backing field for the Id property by using the field: specifier on the attribute, as shown in the following example:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    [field:NonSerialized]
    public int Id { get; set; }

    public string FullName => $"{FirstName} {LastName}";
}

이 기술은 자동 구현 속성의 지원 필드에 연결하는 모든 특성에서 작동합니다.This technique works for any attribute you attach to the backing field on the auto-implemented property.

INotifyPropertyChanged 구현Implementing INotifyPropertyChanged

속성 접근자에서 코드를 작성해야 하는 최종 시나리오는 값이 변경되었다고 데이터 바인딩 클라이언트에 알리는 데 사용되는 INotifyPropertyChanged 인터페이스를 지원하는 것입니다.A final scenario where you need to write code in a property accessor is to support the INotifyPropertyChanged interface used to notify data binding clients that a value has changed. 속성의 값이 변경되면 개체가 INotifyPropertyChanged.PropertyChanged 이벤트를 발생시켜 변경되었음을 나타냅니다.When the value of a property changes, the object raises the INotifyPropertyChanged.PropertyChanged event to indicate the change. 데이터 바인딩 라이브러리가 해당 변경 내용에 따라 차례로 표시 요소를 업데이트합니다.The data binding libraries, in turn, update display elements based on that change. 아래 코드는 이 person 클래스의 FirstName 속성에 대해 INotifyPropertyChanged를 구현하는 방법을 보여 줍니다.The code below shows how you would implement INotifyPropertyChanged for the FirstName property of this person class.

public class Person : INotifyPropertyChanged
{
    public string FirstName
    {
        get => firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("First name must not be blank");
            if (value != firstName)
            {
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(FirstName)));
            }
            firstName = value;
        }
    }
    private string firstName;

    public event PropertyChangedEventHandler PropertyChanged;
    // remaining implementation removed from listing
}

?. 연산자를 null 조건부 연산자라고 합니다.The ?. operator is called the null conditional operator. 연산자의 오른쪽을 평가하기 전에 null 참조를 확인합니다.It checks for a null reference before evaluating the right side of the operator. 최종 결과로, PropertyChanged 이벤트에 대한 구독자가 없는 경우 이벤트를 발생시키는 코드가 실행되지 않습니다.The end result is that if there are no subscribers to the PropertyChanged event, the code to raise the event doesn't execute. 해당 경우 이 확인을 수행하지 않으면 NullReferenceException이 throw됩니다.It would throw a NullReferenceException without this check in that case. 자세한 내용은 events를 참조하세요.For more information, see events. 또한 이 예제에서는 새 nameof 연산자를 사용하여 속성 이름 기호에서 해당 텍스트 표현으로 변환합니다.This example also uses the new nameof operator to convert from the property name symbol to its text representation. nameof를 사용하면 속성 이름을 잘못 입력하는 오류를 줄일 수 있습니다.Using nameof can reduce errors where you have mistyped the name of the property.

INotifyPropertyChanged를 구현하는 작업은 필요한 시나리오를 지원하기 위해 접근자의 코드를 작성할 수 있는 경우의 예제입니다.Again, implementing INotifyPropertyChanged is an example of a case where you can write code in your accessors to support the scenarios you need.

요약Summing up

속성은 클래스 또는 개체에 있는 스마트 필드의 한 형태입니다.Properties are a form of smart fields in a class or object. 개체 외부에서는 개체의 필드와 유사하게 나타납니다.From outside the object, they appear like fields in the object. 그러나 C# 기능의 전체 팔레트를 사용하여 속성을 구현할 수 있습니다.However, properties can be implemented using the full palette of C# functionality. 유효성 검사, 다른 액세스 가능성, 지연 평가 또는 시나리오에 필요한 모든 요구 사항을 제공할 수 있습니다.You can provide validation, different accessibility, lazy evaluation, or any requirements your scenarios need.