Использование свойств (Руководство по программированию в C#)

Свойства сочетают в себе возможности полей и методов. Для пользователя объекта свойство, как представляется, является полем; для доступа к свойству требуется тот же синтаксис. Для реализации класса свойство является одним или двумя блоками кода, представляющим get метод доступа и (или) set или init метод доступа. Блок кода для метода доступа выполняется при чтении свойства; блок кода для getsetinit или метод доступа выполняется при назначении свойства значения. Свойство без метода доступа 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 оптимизирован для чтения памяти этого поля. Автоматически реализованные свойства следуют этому шаблону и получают преимущества от этих оптимизаций. Однако метод виртуального 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
*/

См. также