Использование свойств (Руководство по программированию в C#)
Свойства сочетают в себе возможности полей и методов. Для пользователя объекта свойство, как представляется, является полем; для доступа к свойству требуется тот же синтаксис. Для разработчика класса свойство состоит из одного или двух блоков кода, представляющих get
метод доступа и/или метод доступа set
или init
. Блок кода для аксессора get
выполняется при чтении свойства; блок кода для аксессоров set
или init
выполняется при назначении свойству значения. Свойство без аксессора set
считается доступным только для чтения. Свойство без метода доступа get
считается доступным только для записи. Свойство, у которого имеются оба аксессора, является доступным для чтения и записи. Вы можете использовать init
метод доступа вместо set
метода доступа, чтобы свойство было задано как часть инициализации объектов, но в противном случае сделайте его доступным только для чтения.
В отличие от полей, свойства не классифицируются как переменные. Таким образом, нельзя передать свойство в качестве 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
использует частное поле. Реальное расположение данных свойства часто называется "резервным хранилищем". Обычно свойства используют частные поля в качестве резервного хранилища. Поле помечается как частное для того, чтобы гарантировать возможность его изменения только посредством вызова свойства. Дополнительные сведения об ограничениях открытого и закрытого доступа см. в разделе Модификаторы доступа. Автоматически реализованные свойства предоставляют упрощенный синтаксис для простых объявлений свойств. Дополнительные сведения см. в разделе "Автоматически реализованные свойства".
Начиная с C# 13, можно использовать свойства с поддержкой полей для добавления проверки в аксессор автоматически реализованного свойства, как показано в следующем примере:
public class DateExample
{
public int Month
{
get;
set
{
if ((value > 0) && (value < 13))
{
field = value;
}
}
}
}
Внимание
Ключевое field
слово — это предварительная версия функции в C# 13. Для использования контекстного ключевого слова field
, необходимо использовать .NET 9 и задать элемент <LangVersion>
на preview
в файле проекта.
Следует с осторожностью использовать функцию field
ключевого слова в классе, в котором есть поле с именем field
. Новое field
ключевое слово скрывает поле с именем field
в пределах аксессора свойства. Можно изменить имя переменной field
или использовать @
маркер для ссылки на field
идентификатор как @field
. Дополнительные сведения см. в спецификации компонента для ключевого field
слова.
Аксессор get
Тело аксессора get
похоже на тело метода. Оно должно возвращать значение заданного типа свойства. Компилятор C# и JIT-компилятор обнаруживают распространенные шаблоны для реализации get
аксессора и оптимизируют эти шаблоны. Например, метод доступа, возвращающий поле без выполнения каких-либо вычислений, скорее всего, get
оптимизирован для чтения памяти этого поля. Автоматически реализованные свойства соответствуют этому шаблону и получают преимущества от этих оптимизаций. Однако метод виртуального get
доступа не может быть встроен, поскольку компилятор не знает во время компиляции, какой метод может вызываться в момент выполнения. В следующем примере показан аксессор get
, который возвращает значение закрытого поля _name
.
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, и выполнение не может выйти за пределы тела метода доступа.
Предупреждение
Обычно плохой стиль программирования — изменять состояние объекта через 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
Ошибка заключается в использовании неявного имени параметра value
для объявления локальной переменной в аксессоре set
.
Аксессор init
Код для создания метода доступа init
аналогичен коду для создания метода доступа set
, за исключением того, что используется ключевое слово init
вместо set
. Различие заключается в том, что метод доступа init
можно использовать только в конструкторе или с помощью инициализатора объекта.
Замечания
Свойства могут быть помечены как public
, private
, protected
, internal
, protected internal
или private protected
. Эти модификаторы доступа определяют, каким образом пользователи класса смогут получать доступ к свойству. Аксессоры get
и set
для той же свойства могут иметь разные модификаторы доступа. Например, get
может быть public
, чтобы разрешить доступ только для чтения извне типа, и set
может быть private
или protected
. Дополнительные сведения см. в статье Модификаторы доступа.
Свойство можно объявить как статическое свойство с помощью ключевого static
слова. Статические свойства доступны вызывающим объектам в любое время, даже при отсутствии экземпляра класса. Дополнительные сведения см. в статье Статические классы и члены статических классов.
Свойство можно пометить как виртуальное свойство с помощью виртуального ключевого слова. Виртуальные свойства позволяют производным классам изменять поведение свойства, используя ключевое слово override. Дополнительные сведения об этих параметрах см. в разделе Наследование.
Свойство, переопределяющее виртуальное свойство, также можно запечатывать, указывая, что для производных классов он больше не является виртуальным. Наконец, свойство можно объявить абстрактным (abstract). Абстрактные свойства не определяют реализацию в классе, а производные классы должны писать собственную реализацию. Дополнительные сведения об этих параметрах см. в разделе Абстрактные и запечатанные классы и члены классов.
Примечание.
Использование модификаторов virtual, abstract или override в аксессоре статического static свойства является ошибкой.
Примеры
В этом примере демонстрируются свойства экземпляра, а также статические и доступные только для чтения свойства. Этот метод принимает введенное с клавиатуры имя сотрудника, увеличивает значение 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: {m1.Name}");
System.Console.WriteLine($"Name in the base class is: {((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
и переопределяют его абстрактное свойство 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 = {s.Area:F2}");
System.Console.WriteLine($"Area of the cube = {c.Area:F2}");
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 = {s.side:F2}");
System.Console.WriteLine($"Side of the cube = {c.side:F2}");
}
}
/* 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
*/