Поделиться через


Свойства (Руководство по программированию в C#)

Свойство — это элемент, предоставляющий гибкий механизм для чтения, записи или вычисления значения поля данных. Свойства отображаются как открытые элементы данных, но они реализуются как специальные методы, называемые аксессорами. Эта функция позволяет вызывающим пользователям легко получать доступ к данным и по-прежнему способствовать обеспечению безопасности и гибкости данных. Синтаксис свойств является естественным расширением полей. Поле определяет место хранения:

public class Person
{
    public string? FirstName;

    // Omitted for brevity.
}

Автоматически реализованные свойства

Определение свойства содержит объявления для методов доступа get и set, которые получают и устанавливают значение этого свойства:

public class Person
{
    public string? FirstName { get; set; }

    // Omitted for brevity.
}

В предыдущем примере показано автоматически реализованное свойство. Компилятор создает скрытое поле резервного копирования для свойства. Компилятор также реализует тело аксессоров get и set. Все атрибуты применяются к автоматически реализуемому свойству. Атрибут можно применить к поле резервной копии, созданному компилятором, указав field: тег атрибута.

Можно инициализировать свойство до значения, отличного от значения по умолчанию, задав значение после закрывающей фигурной скобки свойства. Вы можете предпочесть, чтобы начальным значением свойства FirstName была пустая строка, чем null. Как показано в следующем коде, это можно указать следующим образом:

public class Person
{
    public string FirstName { get; set; } = string.Empty;

    // Omitted for brevity.
}

Свойства, поддерживаемые полем

В C# 13 можно добавить проверку или другую логику в метод доступа для свойства с помощью функции предварительного field просмотра ключевых слов. Ключевое field слово обращается к синтезированному полю резервного копирования компилятора для свойства. Он позволяет создавать метод доступа к свойствам без явного объявления отдельного поля резервного копирования.

public class Person
{
    public string? FirstName 
    { 
        get;
        set => field = value.Trim(); 
    }

    // Omitted for brevity.
}

Внимание

Ключевое field слово — это предварительная версия функции в C# 13. Чтобы использовать контекстное ключевое слово field, вы должны использовать .NET 9 и установить элемент <LangVersion> на preview в вашем файле проекта.

Следует осторожно использовать возможность ключевого слова field в классе, имеющем поле под именем field. Новое field ключевое слово скрывает поле с именем field в контексте функции доступа к свойству. Можно изменить имя переменной field или использовать @ маркер для ссылки на field идентификатор как @field. Дополнительные сведения см. в спецификации компонента для ключевого field слова.

Обязательные свойства

В приведенном выше примере вызывающий объект может создать экземпляр Person с использованием конструктора по умолчанию, не устанавливая свойство FirstName. Свойство изменило тип на строку, которая может быть null. Начиная с C# 11, можно требовать, чтобы вызывающие пользователи могли задать свойство:

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName) => FirstName = firstName;

    public required string FirstName { get; init; }

    // Omitted for brevity.
}

Предыдущий код вносит два изменения в Person класс. Во-первых, FirstName объявление свойства включает required модификатор. Это означает, что любой код, создающий новое Person свойство, должен задать это свойство с помощью инициализатора объектов. Во-вторых, конструктор, принимаюющий firstName параметр, имеет System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute атрибут. Этот атрибут сообщает компилятору, что этот конструктор задает всеrequired элементы. Вызывающим этот конструктор не обязательно использовать инициализатор объектов для задания свойств required.

Внимание

Не путайте required с ненулевой. Допустимо задать свойству required значение null или default. Если тип не допускает значение NULL, например string в этих примерах, компилятор выдает предупреждение.

var aPerson = new Person("John");
aPerson = new Person { FirstName = "John"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();

Определения текста выражений

Методы доступа к свойствам часто состоят из однострочных инструкций. Методы доступа присваивают или возвращают результат выражения. Эти свойства можно реализовать как члены, воплощающие выражение. Определения тела выражения состоят из маркера =>, за которым следует выражение, которое назначается свойству или извлекается из него.

Свойства, доступные только для чтения, могут реализовать get аксессор в виде выражения. В следующем примере реализуется свойство только для Name чтения в качестве элемента, наследуемого выражением:

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    public string Name => $"{FirstName} {LastName}";

    // Omitted for brevity.
}

Name — вычисляемое свойство. Нет резервного поля для Name. Свойство вычисляет его каждый раз.

Управление доступом

В предыдущих примерах показаны свойства чтения и записи. Вы также можете создать свойства только для чтения или задать разные уровни доступа для методов доступа set и get. Предположим, что класс Person должен позволять изменять значение свойства FirstName только в других методах класса. Вы можете предоставить доступность аксессора set для private, вместо internal или public.

public class Person
{
    public string? FirstName { get; private set; }

    // Omitted for brevity.
}

Свойство FirstName можно считывать из любого кода, но его можно назначить только из кода в Person классе.

Вы можете добавить любой ограничивающий модификатор доступа к аксессорам set или get. Модификатор доступа для отдельного метода доступа должен быть более строгим, чем доступ к свойству. Предыдущий код является законным, так как свойство FirstName является public, но свойство set является private. Нельзя объявить private свойство с public аксессором. Свойство также можно объявить как protected, internal, protected internal или даже private.

Существует два специальных модификатора доступа для set аксессоров:

  • Аксессор set может использовать init как модификатор доступа. Этот set аксессор можно вызывать только из инициализатора объекта или конструкторов типа. Это более строго, чем private на set аксессоре.
  • Автоматически реализованное свойство может объявлять get аксессор без set аксессора. В этом случае компилятор позволяет вызывать аксессор только из конструкторов типа. Это более ограничительно, чем init модификатор доступа на set.

Измените Person класс следующим образом:

public class Person
{
    public Person(string firstName) => FirstName = firstName;

    public string FirstName { get; }

    // Omitted for brevity.
}

В предыдущем примере требуется, чтобы вызывающие использовали конструктор, включающий параметр FirstName. Вызывающие не могут использовать инициализаторы объектов для назначения значения свойству. Для поддержки инициализаторов можно сделать аксессор set аксессором init, как показано в следующем коде:

public class Person
{
    public Person() { }
    public Person(string firstName) => FirstName = firstName;

    public string? FirstName { get; init; }

    // Omitted for brevity.
}

Эти модификаторы часто используются с модификатором required для принудительной инициализации.

Свойства с резервными полями

Вы можете смешать концепцию вычисляемого свойства с частным полем и создать кэшированное вычисляемое свойство. Например, обновите FullName свойство таким образом, чтобы строковое форматирование происходило при первом доступе:

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

Эта реализация работает, потому что свойства FirstName и LastName доступны только для чтения. Люди могут изменить свое имя. Обновление свойств FirstName и LastName для разрешения set доступа требует аннулирования любого кэшированного значения для fullName. Вы изменяете set и FirstName методы доступа свойства LastName, чтобы fullName поле вычислялось снова:

public class Person
{
    private string? _firstName;
    public string? FirstName
    {
        get => _firstName;
        set
        {
            _firstName = value;
            _fullName = null;
        }
    }

    private string? _lastName;
    public string? LastName
    {
        get => _lastName;
        set
        {
            _lastName = value;
            _fullName = null;
        }
    }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

Эта окончательная версия вычисляет свойство FullName только при необходимости. Если это допустимо, используется ранее вычисляемая версия. В противном случае вычисление обновляет кэшированное значение. Разработчикам, использующим этот класс, не нужно знать подробности реализации. Ни одно из этих внутренних изменений не влияет на использование объекта Person.

Начиная с C# 13, можно создавать partial свойства в partial классах. Объявление реализации для partial свойства не может быть автоматически реализованным свойством. Автоматически реализованное свойство использует тот же синтаксис, что и объявление частичного свойства.

Свойства

Свойства — это своего рода интеллектуальные поля в классе или объекте. Из-за пределов объекта они представляются полями в объекте. Однако для реализации свойства можно использовать полную палитру функциональных возможностей C#. Вы можете предоставлять проверку правильности, различные возможности доступа, отложенное вычисление или любые другие требования, необходимые в ваших сценариях.

  • Простые свойства, для которых не требуется пользовательский код доступа, можно реализовать как определения текста выражения или как автоматически реализованные свойства.
  • Свойства позволяют классу предоставлять общий способ получения и задания значений, скрывая при этом код реализации или проверки.
  • Метод доступа get используется для возврата значения свойства, а метод доступа set — для присвоения нового значения. Метод доступа к свойству init используется для назначения нового значения только во время построения объекта. Эти акцессоры могут иметь различные уровни доступа. Дополнительные сведения см. в разделе Ограничение доступности аксессоров.
  • Ключевое слово value используется для определения значения, которое назначается методом доступа set или init.
  • Свойства могут быть доступны для чтения и записи (они имеют оба метода доступа — get и set), только для чтения (они имеют метод доступа get, но не имеют метода доступа set) или только для записи (они имеют метод доступа set, но не имеют метода доступа get). Свойства, доступные только для записи, редки.

Спецификация языка C#

Дополнительные сведения см. в разделе Свойства в Спецификации языка C#. Спецификация языка является авторитетным источником синтаксиса и использования C#.

См. также