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


ключевое слово field в свойствах

Сводка

Расширьте все свойства, чтобы разрешить им ссылаться на автоматически созданное поле резервной копии с помощью нового контекстного ключевого слова field. Теперь свойства могут содержать аксессор без тела наряду с аксессором с телом.

Мотивация

Автоматические свойства позволяют напрямую задавать или получать резервное поле, предоставляя некоторые элементы управления только путем размещения модификаторов доступа на методы доступа. Иногда необходимо иметь дополнительный контроль над тем, что происходит в одном или обоих аксессорах, но это создает для пользователей необходимость объявлять резервное поле. Затем имя резервного поля должно быть синхронизировано со свойством, а резервное поле ограничивается всем классом, что может привести к случайному обходу методов доступа из класса.

Существует несколько распространенных сценариев. В геттере выполняется отложенная инициализация или используются значения по умолчанию, когда свойство никогда не было задано. В сеттере применяется ограничение для обеспечения допустимости значения или отслеживания и передачи обновлений, таких как вызов события INotifyPropertyChanged.PropertyChanged.

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

Глоссарий

  • автоматическое свойство: сокращение от "автоматически реализуемого свойства" (§15.7.4). Аксессоры в автоматическом свойстве не имеют тела. Реализация и резервное хранилище предоставляются компилятором. Автоматические свойства имеют { get; }, { get; set; }или { get; init; }.

  • автоматически реализованный аксессор: сокращение "автоматически реализованный акцессор". Это акцессор, который не имеет тела. Реализация и резервное хранилище предоставляются компилятором. get;, set; и init; являются автоматическими средствами доступа.

  • полный аксессор: это аксессор, имеющий тело. Реализация не предоставляется компилятором, хотя резервное хранилище по-прежнему может быть (как в примере set => field = value;).

  • свойство с поддержкой поля: это либо свойство с использованием ключевого слова field в теле метода доступа, либо автоматическое свойство.

  • вспомогательное поле: это переменная, обозначенная ключевым словом field в аксессорах свойства, которая также неявно считывается или записывается автоматически реализованными аксессорами (get;, set;или init;).

Подробный дизайн

Для свойств с акцессором init всё, что применяется ниже к set, применяется к акцессору init.

Существует два изменения синтаксиса:

  1. Существует новое контекстное ключевое слово, field, которое может использоваться в телах метода доступа к свойствам для доступа к резервному полю для объявления свойства (решение LDM).

  2. Теперь свойства могут смешивать и сопоставлять автоматические методы доступа с полными средствами доступа (решения LDM). "Автоматическое свойство" будет продолжать означать свойство, у которого методы доступа не имеют тела. Ни один из приведенных ниже примеров не будет считаться автоматическим.

Примеры:

{ get; set => Set(ref field, value); }
{ get => field ?? parent.AmbientValue; set; }

Обе функции доступа могут быть полными, при этом один или оба используют field:

{ get => field; set => field = value; }
{ get => field; set => throw new InvalidOperationException(); }
{ get => overriddenValue; set => field = value; }
{
    get;
    set
    {
        if (field == value) return;
        field = value;
        OnXyzChanged();
    }
}

Свойства с телом выражения и свойства, имеющие только get аксессор, также могут использовать field:

public string LazilyComputed => field ??= Compute();
public string LazilyComputed { get => field ??= Compute(); }

Свойства, доступные только для установки, также могут использовать field:

{
    set
    {
        if (field == value) return;
        field = value;
        OnXyzChanged(new XyzEventArgs(value));
    }
}

Критические изменения

Существование контекстного ключевого слова field в телах метода доступа к свойствам является потенциально критическим изменением.

Так как field является ключевым словом, а не идентификатором, он может быть "затенен" идентификатором, используя обычный маршрут экранирования ключевых слов: @field. Все идентификаторы с именем field, объявленные в телах метода доступа к свойствам, могут защититься от разрывов при обновлении версий C# до 14, добавив начальный @.

Если переменная с именем field объявлена в методе доступа к свойству, сообщается об ошибке.

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

Атрибуты, ориентированные на поля

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

[field: Xyz]
public string Name => field ??= Compute();

[field: Xyz]
public string Name { get => field; set => field = value; }

Атрибут, имеющий отношение к полю, остается недопустимым, если акцессор не использует основное поле:

// ❌ Error, will not compile
[field: Xyz]
public string Name => Compute();

Инициализаторы свойств

Свойства с инициализаторами могут использовать field. Вспомогательное поле инициализируется напрямую, вместо вызова сеттера (решения LDM).

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

Это обеспечивает гибкий контроль над инициализацией. Если вы хотите инициализировать без вызова метода задания, используйте инициализатор свойств. Если вы хотите инициализировать, вызвав метод-сеттер, назначьте свойству начальное значение в конструкторе.

Ниже приведен пример того, где это полезно. Мы считаем, что ключевое слово field будет часто использоваться с моделями представления благодаря элегантному решению, которое оно предлагает для шаблона INotifyPropertyChanged. Сеттеры свойств модели, скорее всего, связаны с пользовательским интерфейсом и, вероятно, вызовут отслеживание изменений или активируют другое поведение. Следующий код должен инициализировать значение по умолчанию IsActive без установки HasPendingChanges на true:

class SomeViewModel
{
    public bool HasPendingChanges { get; private set; }

    public bool IsActive { get; set => Set(ref field, value); } = true;

    private bool Set<T>(ref T location, T value)
    {
        if (RuntimeHelpers.Equals(location, value))
            return false;

        location = value;
        HasPendingChanges = true;
        return true;
    }
}

Это различие в поведении между инициализатором свойств и назначением из конструктора также можно увидеть на примере виртуальных авто-свойств в предыдущих версиях языка.

using System;

// Nothing is printed; the property initializer is not
// equivalent to `this.IsActive = true`.
_ = new Derived();

class Base
{
    public virtual bool IsActive { get; set; } = true;
}

class Derived : Base
{
    public override bool IsActive
    {
        get => base.IsActive;
        set
        {
            base.IsActive = value;
            Console.WriteLine("This will not be reached");
        }
    }
}

Назначение конструктора

Как и в случае с авто-свойствами, назначение в конструкторе вызывает сеттер (потенциально виртуального), если он существует, а если сеттер отсутствует, происходит непосредственное назначение резервному полю.

class C
{
    public C()
    {
        P1 = 1; // Assigns P1's backing field directly
        P2 = 2; // Assigns P2's backing field directly
        P3 = 3; // Calls P3's setter
        P4 = 4; // Calls P4's setter
    }

    public int P1 => field;
    public int P2 { get => field; }
    public int P4 { get => field; set => field = value; }
    public int P3 { get => field; set; }
}

Строгое назначение в структурах

Несмотря на то, что на них нельзя ссылаться в конструкторе, резервные поля, обозначаемые ключевым словом field, подвергаются инициализации по умолчанию и по умолчанию отключены предупреждения в тех же условиях, что и любые другие поля структуры (решение LDM 1, решение LDM 2).

Например, эти диагностические сообщения по умолчанию отключены:

public struct S
{
    public S()
    {
        // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
        // assignments of 'default' to non-explicitly assigned fields.
        _ = P1;
    }

    public int P1 { get => field; }
}
public struct S
{
    public S()
    {
        // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
        // assignments of 'default' to non-explicitly assigned fields.
        P2 = 5;
    }

    public int P2 { get => field; set => field = value; }
}

Свойства, возвращающие ссылки

Как и в случае с автоматическими свойствами, ключевое слово field не будет доступно для использования в свойствах, возвращающих ссылки. Возвращаемые свойства ref не могут иметь наборы доступа, а без набора метода доступа метод доступа и инициализатор свойств — единственные вещи, способные получить доступ к полю резервного копирования. В отсутствие вариантов использования сейчас не время для того, чтобы свойства с возвратом ссылки могли быть записаны как автоматические свойства.

Возможность null

Принцип функционала Nullable Reference Types заключается в понимании существующих идиоматических шаблонов кодирования в C# и стремлении не требовать дополнительных формальностей вокруг этих шаблонов. Предложение по ключевому слову field позволяет использовать простые идиоматические шаблоны для решения часто запрашиваемых сценариев, например, лениво инициализируемые свойства. Важно, чтобы типы ссылок, допускающие значение NULL, хорошо интегрировались с этими новыми шаблонами кодирования.

Целей:

  • Разумный уровень безопасности null должен быть обеспечен для различных шаблонов использования функции ключевого слова field.

  • Шаблоны, использующие ключевое слово field, должны чувствовать себя так, как будто они всегда были частью языка. Избегайте заставлять пользователя проделывать лишние шаги, чтобы включить типы ссылок, поддерживающих значение NULL, в коде, который идеально подходит для функции ключевого слова field.

Одним из ключевых сценариев являются лениво инициализированные свойства:

public class C
{
    public C() { } // It would be undesirable to warn about 'Prop' being uninitialized here

    string Prop => field ??= GetPropValue();
}

Следующие правила nullability применяются не только к свойствам, которые используют ключевое слово field, но и к существующим автоматическим свойствам.

Наличие NULL в поле данных для резервного поля

См. глоссарий для определений новых терминов.

Поле резервного поля имеет тот же тип, что и свойство. Однако его аннотация null может отличаться от свойства. Чтобы определить эту аннотацию, допускающую NULL, мы введем концепцию устойчивости к NULL. Устойчивость к null интуитивно означает, что get аксессор свойства сохраняет безопасность при null, даже если поле содержит значение default для своего типа.

Свойство , поддерживаемое полем, определяется как устойчивых к значению NULL или нет путем выполнения специального анализа доступа к .

  • В целях этого анализа временно предполагается, что field имеет аннотацию с неопределенностью, как, например, string?. Это может привести к тому, что field окажется в начальном состоянии , возможно null, или , возможно по умолчанию, в аксессоре get, в зависимости от его типа.
  • Затем, если анализ, допускающий значение NULL, метод получения не выдает предупреждений, допускающих значение NULL, свойство устойчивых к значению NULL. В противном случае это не устойчиво к значению null.
  • Если свойство не имеет метода доступа get, оно автоматически устойчиво к null.
  • Если метод доступа получается автоматически, свойство не является устойчивым к значению NULL.

Значение NULL для резервного поля определяется следующим образом:

  • Если поле имеет атрибуты nullability, такие как [field: MaybeNull], AllowNull, NotNullили DisallowNull, то аннотация допускаемости значения null для поля совпадает с аннотацией допускаемости значения null для свойства.
    • Это связано с тем, что когда пользователь начинает применять атрибуты NULL к полю, мы больше не хотим выводить что-либо, мы просто хотим, чтобы значение NULL было то, что пользователь сказал.
  • Если содержащее свойство имеет необременяемость или аннотированную nullable, то резервное поле имеет такую же nullable, как и свойство.
  • Если содержащееся свойство имеет неаннотированную nullability (например, string или T) или имеет атрибут [NotNull], а свойство устойчиво к null, то резервное поле имеет аннотированную nullability.
  • Если содержащееся свойство имеет неаннотированную nullability (например, string или T) или имеет атрибут [NotNull], а свойство неустойчивое к значению NULL, то в вспомогательном поле неаннотированная nullability.

Анализ конструктора

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

Мы обновляем формулировки спецификаций, переходя от предыдущего предлагаемого подхода к подходу для достижения этой цели.

При каждом явном или неявном возврате в конструкторе мы предоставляем предупреждение для каждого члена, состояние потока которого несовместимо с его заметками и атрибутами null. Если элемент является свойством, поддерживаемым полем, для этой проверки используется заметка, допускающая значение NULL. В противном случае используется аннотация nullable для самого члена. Разумный прокси-сервер для этого: если назначение элемента самому себе в точке возврата приведет к возникновению предупреждения о допустимости null, то предупреждение о допустимости null будет создано в точке возврата.

Обратите внимание, что это, по сути, ограниченный межпросходный анализ. Мы ожидаем, что для анализа конструктора необходимо выполнить анализ привязки и анализа устойчивости к null для всех применимых аксессоров get в том же типе, которые используют контекстное ключевое слово field и имеют не аннотированную null-значимость. Мы предполагаем, что это не является чрезмерно дорогим, так как тела методов-получателей обычно не очень сложны, и что анализ "устойчивости к null" должен выполняться только один раз независимо от количества конструкторов в типе.

Анализ задания

Для простоты мы используем термины setter и set accessor, чтобы ссылаться на метод доступа set или init.

Необходимо проверить, что сеттеры свойств , имеющих поддержку полей, фактически инициализируют резервное поле.

class C
{
    string Prop
    {
        get => field;

        // getter is not null-resilient, so `field` is not-annotated.
        // We should warn here that `field` may be null when exiting.
        set { }
    }

    public C()
    {
        Prop = "a"; // ok
    }

    public static void Main()
    {
        new C().Prop.ToString(); // NRE at runtime
    }
}

Начальное состояние потока резервного поля в методе задания свойства с поддержкой полей определяется следующим образом:

  • Если свойство имеет инициализатор, начальное состояние потока совпадает с состоянием потока свойства после посещения инициализатора.
  • В противном случае начальное состояние потока совпадает с состоянием потока, заданным field = default;.

При каждом явном или неявном "возврате" в методе установки сообщается предупреждение, если состояние потока резервного поля несовместимо с его аннотациями и атрибутами нулевой допустимости.

Замечания

Эта формулировка намеренно похожа на обычные поля в конструкторах. По сути, поскольку только методы доступа к свойствам могут на самом деле ссылаться на резервное поле, метод задания рассматривается как "мини-конструктор" для резервного поля.

Как и в обычных полях, мы обычно знаем, что свойство было инициализировано в конструкторе, так как оно было задано, но не обязательно. Простое возвращение на ветке, где Prop != null было истинно, также вполне подходит для анализа конструктора, поскольку мы понимаем, что, возможно, использовались неотслеживаемые механизмы для задания свойства.

Были рассмотрены альтернативные варианты; См. раздел альтернативы nullability.

nameof

В местах, где field является ключевым словом, nameof(field) не будет компилироваться (решение LDM), например nameof(nint). Это не похоже на nameof(value), который следует использовать, когда установщики свойств вызывают ArgumentException, как это делают некоторые в библиотеках .NET Core. В отличие от этого, nameof(field) не имеет ожидаемых вариантов использования.

Переопределения

Переопределение свойств может использовать field. Такие случаи использования field ссылаются на поле резервного копирования для свойства, переопределяющего, отдельно от поля резервного копирования базового свойства, если оно есть. Нет ABI для предоставления резервного поля базового свойства классам, переопределяющим его, так как это приведет к нарушению инкапсуляции.

Как и с автоматическими свойствами, свойства, использующие ключевое слово field и переопределение базового свойства, должны переопределить все методы доступа (решения LDM).

Захваты

field должны быть захвачены в локальных функциях и лямбда-выражениях, а ссылки на field из локальных функций и лямбда-выражений допускаются, даже если нет других ссылок (решение LDM 1, решение LDM 2):

public class C
{
    public static int P
    {
        get
        {
            Func<int> f = static () => field;
            return f();
        }
    }
}

Предупреждения об использовании полей

Если ключевое слово field используется в аксессоре, существующий анализ компилятора неназначенных или непрочитанных полей будет охватывать это поле.

  • CS0414: полю резервного копирования для свойства "Xyz" назначается значение, но оно никогда не используется.
  • CS0649: поле резервного копирования для свойства "Xyz" никогда не назначается, и всегда будет иметь значение по умолчанию

Изменения спецификации

Синтаксис

При компиляции с языком версии 14 или более поздней field считается ключевым словом при использовании в качестве первичного выражения (решения LDM) в следующих расположениях (решение LDM):

  • В телах методов доступа accessor get, setи init в свойствах , но не в индексаторах.
  • Атрибуты, применяемые к этим акцессорам
  • В вложенных лямбда-выражениях и локальных функциях, а также в выражениях LINQ в этих средствах доступа

Во всех остальных случаях, в том числе при компиляции с использованием языка версии 12 или более поздней, field считается идентификатором.

primary_no_array_creation_expression
    : literal
+   | 'field'
    | interpolated_string_expression
    | ...
    ;

Свойства

§15.7.1свойства — общие

property_initializer могут быть предоставлены только для автоматически реализованного свойства, а такжесвойству с резервным полем, которое будет выдано. property_initializer вызывает инициализацию базового поля таких свойств со значением, заданным выражением .

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

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

  1. аксессор только с телом из точки с запятой
  2. использование контекстного ключевого слова field в объектах доступа или тексте выражениясвойства

Если свойство указывается как автоматически реализованное свойство, скрытое неназванное поле резервного копирования автоматически доступно для свойства , а методы доступа реализуются для чтения и записи в это резервное поле. Для автоматических свойств любой метод доступа только с запятой get реализуется для чтения, а любой метод доступа только с запятойset для записи в его резервное поле.

Скрытое резервное поле недоступно, оно может быть прочитано и записано только с помощью автоматически реализованных методов доступа к свойствам, даже в пределах содержащего типа.Поле резервного копирования можно ссылаться непосредственно с помощью ключевого слова fieldво всех методах доступа и в теле выражения свойства. Так как поле не именуется, его нельзя использовать в выраженииnameof.

Если свойство автосвязи нетолько метод доступа с запятой,, то резервное поле считается readonly (§15.5.3). Как и поле readonly, автоматическое свойство только для чтения (без модификатора set или модификатора init) также может быть назначено в теле конструктора заключающего класса. Назначение производится непосредственно в резервное поле свойства, предназначенное только для чтения.

Автоматическое свойство не может иметь только один метод доступа с точкой с запятой set без другого метода доступа get.

При желании автоматическое свойство может иметь property_initializer, который применяется непосредственно к резервному полю в виде variable_initializer (§17.7).

Следующий пример:

// No 'field' symbol in scope.
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

эквивалентен следующему объявлению:

// No 'field' symbol in scope.
public class Point
{
    public int X { get { return field; } set { field = value; } }
    public int Y { get { return field; } set { field = value; } }
}

эквивалентно следующему:

// No 'field' symbol in scope.
public class Point
{
    private int __x;
    private int __y;
    public int X { get { return __x; } set { __x = value; } }
    public int Y { get { return __y; } set { __y = value; } }
}

Следующий пример:

// No 'field' symbol in scope.
public class LazyInit
{
    public string Value => field ??= ComputeValue();
    private static string ComputeValue() { /*...*/ }
}

эквивалентен следующему объявлению:

// No 'field' symbol in scope.
public class Point
{
    private string __value;
    public string Value { get { return __value ??= ComputeValue(); } }
    private static string ComputeValue() { /*...*/ }
}

Альтернативы

Альтернативные варианты null

Помимо подхода устойчивости нулей, описанного в разделе Nullability, рабочая группа предложила LDM рассмотреть следующие альтернативы:

Бездействовать

Мы не могли бы ввести специальное поведение здесь. В сущности:

  • Обрабатывайте свойство, поддерживаемое полем, так же, как и автоматические свойства, как это делается сегодня, — его необходимо инициализировать в конструкторе, за исключением случаев, когда свойство помечено как обязательное и т. д.
  • При анализе методов доступа к свойствам не требуется специальная обработка переменной поля. Это просто переменная с тем же типом и возможностью быть null, что и свойство.

Обратите внимание, что это приведет к возникновению бесполезных предупреждений для сценариев "отложенного свойства", в этом случае пользователям, скорее всего, потребуется назначить null! или аналогичное для подавления предупреждений конструктора.
Мы можем рассмотреть подальтернативу полностью игнорировать свойства, используя ключевое слово field для анализа конструктора на null-значения. В этом случае не было бы предупреждений о необходимости инициализации, но и никаких неудобств для пользователя, независимо от того, какой шаблон инициализации он использует.

Поскольку мы планируем выпускать только функцию ключевого слова field в предварительной версии LangVersion в .NET 9, мы рассчитываем иметь возможность изменить nullable-поведение для этой функции в .NET 10. Поэтому мы могли бы рассмотреть возможность принятия решения с низкими затратами в краткосрочной перспективе и постепенно перейти на одно из более сложных решений в долгосрочной перспективе.

атрибуты nullability, ориентированные на field

Мы могли бы ввести следующие значения по умолчанию, достигая разумного уровня безопасности null, без участия какого-либо межпросультного анализа вообще:

  1. Переменная field всегда имеет такую же nullable аннотацию, как и свойство.
  2. Атрибуты нулевости [field: MaybeNull, AllowNull] и т. д. можно использовать для настройки нулевости поля хранения.
  3. Свойства, поддерживаемые полем, проверяются для инициализации в конструкторах на основе заметок и атрибутов, допускающих значение NULL.
  4. Сеттеры в свойства, поддерживаемые полем, проверяют инициализацию field аналогично конструкторам.

Это означает, что "маленький ленивый сценарий" будет выглядеть следующим образом:

class C
{
    public C() { } // no need to warn about initializing C.Prop, as the backing field is marked nullable using attributes.

    [field: AllowNull, MaybeNull]
    public string Prop => field ??= GetPropValue();
}

Одна из причин, по которой мы уклонились от использования атрибутов nullability здесь, заключается в том, что те, которые у нас есть, в основном предназначены для описания входных и выходных данных сигнатур методов. Они являются громоздкими для описания допустимости значений NULL для долгоживующих переменных.

  • На практике требуется [field: MaybeNull, AllowNull], чтобы поле вело себя "разумно" как пустая переменная, что задает исходное состояние потока как возможно-null и позволяет записывать в него возможные значения null. Это кажется громоздким просить пользователей выполнять действия для относительно частых "простых ленивых" сценариев.
  • Если бы мы выбрали этот подход, мы бы рассмотрели добавление предупреждения при использовании [field: AllowNull], предложив также добавить MaybeNull. Это связано с тем, что AllowNull не решает задачу, ради которой пользователи используют переменные, допускающие значение NULL: предполагается, что поле изначально не равно null, даже если в него еще ничего не записывали.
  • Можно также настроить поведение [field: MaybeNull] в ключевом слове field или даже полях в целом, чтобы разрешить запись значений NULL в переменную, как если бы AllowNull также присутствовали неявно.

Ответы на вопросы LDM

Расположения синтаксиса для ключевых слов

В методах доступа, где field и value могут привязаться к автоматически создаваемому полю или неявному параметру сеттера, в каких местах синтаксиса идентификаторы должны рассматриваться как ключевые слова?

  1. всегда
  2. основные выражения только
  3. никогда

Первые два случая — это ломающие изменения.

Если идентификаторы всегда считаются ключевыми словами, то это критическое изменение для следующего примера:

class MyClass
{
    private int field;
    public int P => this.field; // error: expected identifier

    private int value;
    public int Q
    {
        set { this.value = value; } // error: expected identifier
    }
}

Если идентификаторы являются ключевыми словами только при использовании в качестве первичных выражений, критическое изменение становится менее значительным. Наиболее распространенная ошибка может быть некорректным использованием существующего члена с именем field.

class MyClass
{
    private int field;
    public int P => field; // binds to synthesized backing field rather than 'this.field'
}

Также возникает разрыв, когда field или value переопределен в вложенной функции. Это может быть единственным разрывом для value для первичных выражений.

class MyClass
{
    private IEnumerable<string> _fields;
    public bool HasNotNullField
    {
        get => _fields.Any(field => field is { }); // 'field' binds to synthesized backing field
    }
    public IEnumerable<string> Fields
    {
        get { return _fields; }
        set { _fields = value.Where(value => Filter(value)); } // 'value' binds to setter parameter
    }
}

Если идентификаторы никогда не считаются ключевыми словами, то они привязываются только к синтезированному резервному полю или неявному параметру, только если они не привязываются к другим элементам. В этом случае нет значительных изменений.

Ответ

field является ключевым словом в соответствующих аксессорах только при использовании в качестве первичного выражения; value никогда не считается ключевым словом.

Сценарии, аналогичные { set; }

{ set; } в настоящее время запрещено, и это имеет смысл: поле, которое это создает, никогда не может быть прочитано. Теперь есть новые способы оказаться в ситуации, когда сеттер вводит резервное поле, которое никогда не читается, как расширение { set; } в { set => field = value; }.

Какой из этих сценариев должен быть разрешен для компиляции? Предположим, что предупреждение "поле никогда не считывается" будет применяться так же, как и с полем, объявленным вручную.

  1. { set; } - Запрещено сегодня, продолжайте запрещать
  2. { set => field = value; }
  3. { get => unrelated; set => field = value; }
  4. { get => unrelated; set; }
  5. {
        set
        {
            if (field == value) return;
            field = value;
            SendEvent(nameof(Prop), value);
        }
    }
    
  6. {
        get => unrelated;
        set
        {
            if (field == value) return;
            field = value;
            SendEvent(nameof(Prop), value);
        }
    }
    

Ответ

Только запретить то, что уже запрещено сегодня в автоматических свойствах, без тела set;.

field в методе доступа к событиям

Следует ли field быть ключевым словом в методе доступа к событиям и должен ли компилятор создать резервное поле?

class MyClass
{
    public event EventHandler E
    {
        add { field += value; }
        remove { field -= value; }
    }
}

рекомендация: fieldне ключевое слово в акцессоре события, а резервное поле не создается.

Ответ

Рекомендации, принятые. field не ключевое слово в аксессоре события, и скрытое поле не создается.

Нулевое значение field

Следует ли принять предлагаемое значение NULL field? Ознакомьтесь с разделом «Nullability» и открытым вопросом, содержащимся в нём.

Ответ

Принято общее предложение. Определенное поведение по-прежнему нуждается в более подробном просмотре.

field в инициализаторе свойств

Должен ли field быть ключевым словом в инициализаторе свойств и привязаться к резервному полю?

class A
{
    const int field = -1;

    object P1 { get; } = field; // bind to const (ok) or backing field (error)?
}

Существуют ли полезные сценарии для ссылки на резервное поле в инициализаторе?

class B
{
    object P2 { get; } = (field = 2);        // error: initializer cannot reference instance member
    static object P3 { get; } = (field = 3); // ok, but useful?
}

В приведенном выше примере привязка к резервному полю должна привести к ошибке: "инициализатор не может ссылаться на нестатическое поле".

Ответ

Мы привязываем инициализатор, как и в предыдущих версиях C#. Мы не будем помещать резервное поле в область видимости и не будем запрещать ссылаться на другие элементы с именем field.

Взаимодействие с частичными свойствами

Инициализаторы

Когда частичное свойство использует field, в каких частях допускается наличие инициализатора?

partial class C
{
    public partial int Prop { get; set; } = 1;
    public partial int Prop { get => field; set => field = value; } = 2;
}
  • Кажется очевидным, что ошибка должна возникать, когда обе части имеют инициализатор.
  • Мы можем представить случаи использования, в которых либо часть, связанная с определением, либо с реализацией, может захотеть задать начальное значение field.
  • Кажется, что если мы разрешаем инициализатор в части определения, он фактически заставляет реализующего использовать field, чтобы программа была допустимой. Это хорошо?
  • Мы считаем, что генераторы будут использовать field всякий раз, когда в реализации требуется резервное поле того же типа. Это частично связано с тем, что генераторы часто хотят, чтобы пользователи могли использовать целевые атрибуты [field: ...] в разделе определения свойств. При использовании ключевого слова field это избавляет разработчика генератора от необходимости перенаправления таких атрибутов в сгенерированное поле и подавляет предупреждения относительно свойства. Эти же генераторы, скорее всего, также хотят разрешить пользователю указать начальное значение поля.

рекомендация: Разрешить инициализатор в обеих частях частичного свойства, когда часть реализации использует field. Сообщите об ошибке, если обе части имеют инициализатор.

Ответ

Рекомендация принята. Объявление или реализация расположений свойств может использовать инициализатор, но не одновременно.

Автоматические аксессоры

Как исходно задумано, реализация частичного свойства должна иметь тела для всех аксессоров. Однако в последних итерациях функции ключевого слова field было введено понятие "автоаксессоров". Могут ли реализации частичных свойств использовать такие методы доступа? Если они используются исключительно, это будет неотличимо от установочного объявления.

partial class C
{
    public partial int Prop0 { get; set; }
    public partial int Prop0 { get => field; set => field = value; } // this is equivalent to the two "semi-auto" forms below.

    public partial int Prop1 { get; set; }
    public partial int Prop1 { get => field; set; } // is this a valid implementation part?

    public partial int Prop2 { get; set; }
    public partial int Prop2 { get; set => field = value; } // what about this? will there be disagreement about which is the "best" style?

    public partial int Prop3 { get; }
    public partial int Prop3 { get => field; } // it will only be valid to use at most 1 auto-accessor, when a second accessor is manually implemented.

Рекомендация: Запретить автоаксессоры в частичных реализациях свойств, так как ограничения, связанные с тем, когда они будут полезны, более запутаны в использовании, чем преимущество их разрешения.

Ответ

Не менее одного реализуемого метода доступа необходимо реализовать вручную, но другой метод доступа можно реализовать автоматически.

Поле readonly

Когда следует рассматривать синтезируемое резервное поле только для чтения?

struct S
{
    readonly object P0 { get => field; } = "";         // ok
    object P1          { get => field ??= ""; }        // ok
    readonly object P2 { get => field ??= ""; }        // error: 'field' is readonly
    readonly object P3 { get; set { _ = field; } }     // ok
    readonly object P4 { get; set { field = value; } } // error: 'field' is readonly
}

Если резервное поле считается только для чтения, поле, передаваемое в метаданные, помечается как initonly, и сообщается об ошибке, если field изменяется, кроме как в инициализаторе или конструкторе.

рекомендация. Синтезируемое поле резервного копирования только для чтения, если содержащий тип является struct, а свойство или содержащий тип объявлен readonly.

Ответ

Рекомендация принимается.

Контекст только для чтения и set

Следует ли позволить использовать акцессор set в контексте readonly для свойства, применяющего field?

readonly struct S1
{
    readonly object _p1;
    object P1 { get => _p1; set { } }   // ok
    object P2 { get; set; }             // error: auto-prop in readonly struct must be readonly
    object P3 { get => field; set { } } // ok?
}

struct S2
{
    readonly object _p1;
    readonly object P1 { get => _p1; set { } }   // ok
    readonly object P2 { get; set; }             // error: auto-prop with set marked readonly
    readonly object P3 { get => field; set { } } // ok?
}

Ответ

Могут существовать сценарии, в которых вы реализуете метод доступа set для структуры readonly и либо передаете его, либо вызываете исключение. Мы позволим этому.

код [Conditional]

Должно ли синтезированное поле создаваться, если field используется только в качестве пропущенных вызовов условных методов ?

Например, следует ли создать вспомогательное поле в случае сборки в режиме, отличном от DEBUG?

class C
{
    object P
    {
        get
        {
            Debug.Assert(field is null);
            return null;
        }
    }
}

Для справки, поля для параметров основного конструктора генерируются в аналогичных случаях — см. sharplab.io.

рекомендация: резервное поле генерируется, когда field используется только в опущенных вызовах условных методов.

Ответ

Conditional код может влиять на безусловный код, например, Debug.Assert изменяет допустимость нулевого значения. Было бы странно, если бы field не имело аналогичного влияния. Это также вряд ли встречается в большинстве случаев кода, поэтому мы поступим проще и примем рекомендацию.

Свойства интерфейса и автоаксессоры

Распознается ли сочетание вручную и автоматически реализованных методов доступа для свойства interface, где автоматически реализованный метод доступа относится к синтезируемому резервному полю?

Для свойства экземпляра сообщается об ошибке, что поля экземпляра не поддерживаются.

interface I
{
           object P1 { get; set; }                           // ok: not an implementation
           object P2 { get => field; set { field = value; }} // error: instance field

           object P3 { get; set { } } // error: instance field
    static object P4 { get; set { } } // ok: equivalent to { get => field; set { } }
}

Рекомендация: Автоаксессоры распознаются в свойствах с номером interface, а автоаксессоры относятся к синтезированному вспомогательному полю. Для свойства экземпляра сообщается об ошибке, что поля экземпляра не поддерживаются.

Ответ

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