속성 사용(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로 표시되었습니다. 공용 및 개인 액세스 제한에 대한 자세한 내용은 액세스 한정자를 참조하세요. 자동으로 구현된 속성은 단순 속성 선언에 대한 간소화된 구문을 제공합니다. 자세한 내용은 자동으로 구현된 속성을 참조 하세요.
C# 13부터 다음 예제와 같이 필드 지원 속성을 사용하여 자동으로 구현된 속성의 접근자에 유효성 set
검사를 추가할 수 있습니다.
public class DateExample
{
public int Month
{
get;
set
{
if ((value > 0) && (value < 13))
{
field = value;
}
}
}
}
Important
field
키워드는 C# 13의 미리 보기 기능입니다. 상황별 키워드를 사용하려면 .NET 9를 preview
사용하고 field
프로젝트 파일에서 요소를 설정 <LangVersion>
해야 합니다.
이름이 지정된 field
필드가 field
있는 클래스에서 키워드 기능을 사용하는 데 주의해야 합니다. 새 field
키워드는 속성 접근자의 범위에 명명된 field
필드를 숨깁니다. 변수의 field
이름을 변경하거나 토큰을 사용하여 @
식별자를 .로 @field
참조 field
할 수 있습니다. 키워드에 대한 field
기능 사양을 읽어 자세히 알아볼 수 있습니다.
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
로 표시될 수 있습니다. 이러한 액세스 한정자는 클래스 사용자가 속성에 액세스하는 방법을 정의합니다. 동일한 속성에 대한 get
및 set
접근자는 다른 액세스 한정자를 가질 수 있습니다. 예를 들어 get
(이)가 public
(을)를 형식 외부에서 읽기 전용 액세스를 허용하도록 할 수 있으며 set
(은)는 private
또는 protected
일 수 있습니다. 자세한 내용은 액세스 한정자를 참조하세요.
static
키워드를 사용하여 속성을 정적 속성으로 선언할 수 있습니다. 클래스의 인스턴스가 없더라도 호출자는 언제든지 정적 속성을 사용할 수 있습니다. 자세한 내용은 static 클래스 및 static 클래스 멤버를 참조하세요.
가상 키워드를 사용하여 속성을 가상 속성으로 표시할 수 있습니다. 가상 속성을 사용하면 파생 클래스가 override 키워드를 사용하여 속성 동작을 재정의할 수 있습니다. 이러한 옵션에 대한 자세한 내용은 상속을 참조하세요.
가상 속성을 재정의하는 속성이 sealed일 수도 있으며, 파생 클래스에 대해 더 이상 가상이 아니도록 지정합니다. 마지막으로, 속성을 abstract로 선언할 수 있습니다. 추상 속성은 클래스의 구현을 정의하지 않으며 파생 클래스는 자체 구현을 작성해야 합니다. 이러한 옵션에 대한 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버를 참조하세요.
예제
이 예제에서는 인스턴스, 정적 및 읽기 전용 속성을 보여 줍니다. 키보드에서 직원 이름을 받고 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 한정자를 참조하세요.
재정의 속성 예제
이 예제에서 두 클래스 Cube
및 Square
는 추상 클래스 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
*/
참고 항목
.NET