Использование свойств (Руководство по программированию в 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
использует частное поле. Реальное расположение данных свойства часто называется "резервным хранилищем". Обычно свойства используют частные поля в качестве резервного хранилища. Поле помечается как частное для того, чтобы гарантировать возможность его изменения только посредством вызова свойства. Дополнительные сведения об ограничениях открытого и закрытого доступа см. в разделе Модификаторы доступа. Автоматически реализованные свойства предоставляют упрощенный синтаксис для простых объявлений свойств. Дополнительные сведения см. в разделе "Автоматически реализованные свойства".
Метод доступа get
Тело метода доступа get
похоже на тело метода. Оно должно возвращать значение заданного типа свойства. Компилятор C# и JIT-компилятор обнаруживают распространенные шаблоны для реализации get
метода доступа и оптимизирует эти шаблоны. Например, метод доступа, возвращающий поле без выполнения каких-либо вычислений, скорее всего, get
оптимизирован для чтения памяти этого поля. Автоматически mplemented свойства следуют этому шаблону и извлекают выгоду из этих оптимизаций. Однако метод виртуального 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
слова. Статические свойства доступны вызывающим в любое время, даже если экземпляр класса не существует. Дополнительные сведения см. в статье Статические классы и члены статических классов.
Свойство можно пометить как виртуальное свойство с помощью виртуального ключевого слова. Виртуальные свойства позволяют производным классам переопределять поведение свойства с помощью ключевого слова переопределения . Дополнительные сведения об этих параметрах см. в разделе Наследование.
Свойство, переопределяющее виртуальное свойство, также можно запечатывать, указывая, что для производных классов он больше не является виртуальным. Наконец, свойство можно объявить абстрактным (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: {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
и переопределяют его абстрактное свойство 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
*/