다음을 통해 공유


속성 사용(C# 프로그래밍 가이드)

속성은 필드 및 메서드 모두의 측면을 결합합니다. 개체의 사용자에게 속성은 필드로 표시되며, 속성에 액세스하려면 동일한 구문이 필요합니다. 클래스의 구현자에 속성은 get 접근자 및/또는 set 또는 init 접근자를 나타내는 하나 또는 두 개의 코드 블록입니다. get 접근자에 대한 코드 블록은 속성을 읽을 때 실행됩니다. 속성에 값이 할당되면 set 또는 init 접근자에 대한 코드 블록이 실행됩니다. set 접근자가 없는 속성은 읽기 전용으로 간주됩니다. get 접근자가 없는 속성은 쓰기 전용으로 간주됩니다. 두 접근자가 모두 있는 속성은 읽기/쓰기입니다. set 접근자 대신 init 접근자를 사용하여 속성을 개체 초기화의 일부로 설정할 수 있지만 그렇지 않으면 읽기 전용으로 설정할 수 있습니다.

필드와 달리 속성은 변수로 분류되지 않습니다. 따라서 속성을 ref 또는 out 매개 변수로 전달할 수 없습니다.

속성에는 다음과 같은 여러 가지 용도가 있습니다.

  • 변경을 허용하기 전에 데이터의 유효성을 검사할 수 있습니다.
  • 데이터베이스와 같은 다른 원본에서 해당 데이터가 검색되는 클래스에 데이터를 투명하게 노출할 수 있습니다.
  • 이벤트를 발생하거나 다른 필드의 값을 변경하는 등 데이터가 변경될 때 작업을 수행할 수 있습니다.

속성은 필드의 액세스 수준, 속성 형식, 속성 이름, get 접근자 및/또는 set 접근자를 선언하는 코드 블록을 차례로 지정하여 클래스 블록에서 선언됩니다. 예시:

public class Date
{
    private int _month = 7;  // Backing store

    public int Month
    {
        get => _month;
        set
        {
            if ((value > 0) && (value < 13))
            {
                _month = value;
            }
        }
    }
}

이 예제에서 Month는 속성으로 선언되었으므로, set 접근자를 통해 Month 값이 1에서 12 사이로 설정되도록 할 수 있습니다. Month 속성은 전용 필드를 사용하여 실제 값을 추적합니다. 속성 데이터의 실제 위치를 속성의 “백업 저장소”라고도 합니다. 일반적으로 속성은 전용 필드를 백업 저장소로 사용합니다. 속성 호출을 통해서만 필드를 변경할 수 있도록 하기 위해 필드는 private로 표시되었습니다. 공용 및 개인 액세스 제한에 대한 자세한 내용은 액세스 한정자를 참조하세요. 자동 구현 속성은 간단한 속성 선언을 위해 간소화된 구문을 제공합니다. 자세한 내용은 자동으로 구현된 속성을 참조하세요.

get 접근자

get 접근자 본문은 메서드 본문과 유사합니다. 속성 형식의 값을 반환해야 합니다. C# 컴파일러 및 JIT(Just-In-Time) 컴파일러는 get 접근자를 구현하기 위한 일반적인 패턴을 검색하고 이러한 패턴을 최적화합니다. 예를 들어 계산을 수행하지 않고 필드를 반환하는 get 접근자가 해당 필드의 메모리 읽기에 최적화될 수 있습니다. 자동 구현 속성은 이 패턴을 따르며 이러한 최적화의 이점을 누릴 수 있습니다. 그러나 컴파일러가 실제로 런타임에 호출될 수 있는 메서드를 컴파일 시간에 알지 못하기 때문에 가상 get 접근자 메서드를 인라인화할 수 없습니다. 다음 예에서는 프라이빗 필드 _name의 값을 반환하는 get 접근자를 보여 줍니다.

class Employee
{
    private string _name;  // the name field
    public string Name => _name;     // the Name property
}

할당 대상을 제외하고 속성을 참조하는 경우 속성 값을 읽기 위해 get 접근자가 호출됩니다. 예시:

var employee= new Employee();
//...

System.Console.Write(employee.Name);  // the get accessor is invoked here

get 접근자는 return 또는 throw 문으로 끝나야 하며, 제어가 접근자 본문을 벗어날 수 없습니다.

Warning

get 접근자를 사용하여 개체의 상태를 변경하는 것은 잘못된 프로그래밍 스타일입니다.

get 접근자를 사용하여 필드 값을 반환하거나 계산한 후 반환할 수 있습니다. 예시:

class Manager
{
    private string _name;
    public string Name => _name != null ? _name : "NA";
}

이전 예제에서는 Name 속성에 값을 할당하지 않으면 NA 값을 반환합니다.

set 접근자

set 접근자는 반환 형식이 void인 메서드와 비슷합니다. 형식이 속성의 형식인 value라는 암시적 매개 변수를 사용합니다. 컴파일러 및 JIT 컴파일러는 set 또는 init 접근자에 대한 일반적인 패턴도 인식합니다. 이러한 일반적인 패턴은 지원 필드에 대한 메모리를 직접 작성하여 최적화됩니다. 다음 예제에서는 set 접근자가 Name 속성에 추가됩니다.

class Student
{
    private string _name;  // the name field
    public string Name    // the Name property
    {
        get => _name;
        set => _name = value;
    }
}

속성에 값을 할당하는 경우 새 값을 제공하는 인수를 사용하여 set 접근자가 호출됩니다. 예시:

var student = new Student();
student.Name = "Joe";  // the set accessor is invoked here

System.Console.Write(student.Name);  // the get accessor is invoked here

set 접근자의 지역 변수 선언에 대해 암시적 매개 변수 이름 value를 사용하면 오류가 발생합니다.

Init 접근자

init 접근자를 만드는 코드는 set 대신 init 키워드를 사용한다는 점을 제외하면 set 접근자를 만드는 코드와 같습니다. 차이점은 init 접근자는 생성자 또는 object-initializer를 통해서만 사용할 수 있다는 것입니다.

설명

속성은 public, private, protected, internal, protected internal 또는 private protected로 표시될 수 있습니다. 이러한 액세스 한정자는 클래스 사용자가 속성에 액세스하는 방법을 정의합니다. 동일한 속성에 대한 getset 접근자는 다른 액세스 한정자를 가질 수 있습니다. 예를 들어 get(이)가 public(을)를 형식 외부에서 읽기 전용 액세스를 허용하도록 할 수 있으며 set(은)는 private 또는 protected일 수 있습니다. 자세한 내용은 액세스 한정자를 참조하세요.

static 키워드를 사용하여 속성을 정적 속성으로 선언할 수 있습니다. 클래스의 인스턴스가 없더라도 호출자는 언제든지 정적 속성을 사용할 수 있습니다. 자세한 내용은 static 클래스 및 static 클래스 멤버를 참조하세요.

가상 키워드를 사용하여 속성을 가상 속성으로 표시할 수 있습니다. 가상 속성을 사용하면 파생 클래스가 override 키워드를 사용하여 속성 동작을 재정의할 수 있습니다. 이러한 옵션에 대한 자세한 내용은 상속을 참조하세요.

가상 속성을 재정의하는 속성이 sealed일 수도 있으며, 파생 클래스에 대해 더 이상 가상이 아니도록 지정합니다. 마지막으로, 속성을 abstract로 선언할 수 있습니다. 추상 속성은 클래스의 구현을 정의하지 않으며 파생 클래스는 자체 구현을 작성해야 합니다. 이러한 옵션에 대한 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버를 참조하세요.

참고 항목

static 속성의 접근자에 virtual, abstract 또는 override 한정자를 사용하면 오류가 발생합니다.

예제

이 예제에서는 인스턴스, 정적 및 읽기 전용 속성을 보여 줍니다. 키보드에서 직원 이름을 받고 NumberOfEmployees를 1만큼 증가한 다음 직원 이름과 번호를 표시합니다.

public class Employee
{
    public static int NumberOfEmployees;
    private static int _counter;
    private string _name;

    // A read-write instance property:
    public string Name
    {
        get => _name;
        set => _name = value;
    }

    // A read-only static property:
    public static int Counter => _counter;

    // A Constructor:
    public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}

숨김 속성 예제

이 예제에서는 파생 클래스에서 이름이 같은 다른 속성에 의해 숨겨진 기본 클래스의 속성에 액세스하는 방법을 보여 줍니다.

public class Employee
{
    private string _name;
    public string Name
    {
        get => _name;
        set => _name = value;
    }
}

public class Manager : Employee
{
    private string _name;

    // Notice the use of the new modifier:
    public new string Name
    {
        get => _name;
        set => _name = value + ", Manager";
    }
}

class TestHiding
{
    public static void Test()
    {
        Manager m1 = new Manager();

        // Derived class property.
        m1.Name = "John";

        // Base class property.
        ((Employee)m1).Name = "Mary";

        System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
        System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
    }
}
/* Output:
    Name in the derived class is: John, Manager
    Name in the base class is: Mary
*/

다음은 앞의 예제에서 중요한 사항입니다.

  • 파생 클래스의 Name 속성은 기본 클래스의 Name 속성을 숨깁니다. 이러한 경우 new 한정자는 파생 클래스의 속성 선언에 사용됩니다.
    public new string Name
    
  • (Employee) 캐스트는 기본 클래스의 숨겨진 속성에 액세스하는 데 사용됩니다.
    ((Employee)m1).Name = "Mary";
    

멤버를 숨기는 방법에 대한 자세한 내용은 new 한정자를 참조하세요.

재정의 속성 예제

이 예제에서 두 클래스 CubeSquare는 추상 클래스 Shape를 구현하고 해당 abstract Area 속성을 재정의합니다. 속성의 override 한정자를 사용합니다. 프로그램은 변을 입력으로 사용하고 사각형과 정육면체의 면적을 계산합니다. 또한 면적을 입력으로 사용하고 사각형 및 정육면체의 해당 변을 계산합니다.

abstract class Shape
{
    public abstract double Area
    {
        get;
        set;
    }
}

class Square : Shape
{
    public double side;

    //constructor
    public Square(double s) => side = s;

    public override double Area
    {
        get => side * side;
        set => side = System.Math.Sqrt(value);
    }
}

class Cube : Shape
{
    public double side;

    //constructor
    public Cube(double s) => side = s;

    public override double Area
    {
        get => 6 * side * side;
        set => side = System.Math.Sqrt(value / 6);
    }
}

class TestShapes
{
    static void Main()
    {
        // Input the side:
        System.Console.Write("Enter the side: ");
        double side = double.Parse(System.Console.ReadLine());

        // Compute the areas:
        Square s = new Square(side);
        Cube c = new Cube(side);

        // Display the results:
        System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
        System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
        System.Console.WriteLine();

        // Input the area:
        System.Console.Write("Enter the area: ");
        double area = double.Parse(System.Console.ReadLine());

        // Compute the sides:
        s.Area = area;
        c.Area = area;

        // Display the results:
        System.Console.WriteLine("Side of the square = {0:F2}", s.side);
        System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
    }
}
/* Example Output:
    Enter the side: 4
    Area of the square = 16.00
    Area of the cube = 96.00

    Enter the area: 24
    Side of the square = 4.90
    Side of the cube = 2.00
*/

참고 항목