Start Debugging

Исправление: CS9035 "Required member 'X' must be set in the object initializer" в C#

CS9035 означает, что член, помеченный как required, не был присвоен. Присвойте его в инициализаторе объекта или добавьте конструктор с [SetsRequiredMembers], который присваивает каждый член required.

CS9035 возникает во время компиляции, когда вы создаёте экземпляр типа, у которого есть член required, но этот член не присваивается. Компилятор требует, чтобы каждое поле или свойство required было установлено до того, как объект покинет конструирование. Исправьте это напрямую: добавьте член в инициализатор объекта, например new Person { Name = "Ada" }. Если конструктор уже присваивает член, сообщите об этом компилятору, поставив [SetsRequiredMembers] на этот конструктор, чтобы он перестал требовать инициализатор. Это проверено на C# 14 и .NET 11; модификатор required и эта диагностика ведут себя одинаково начиная с C# 11 на .NET 7.

Ошибка в контексте

Полное сообщение называет точный член, которого не хватает компилятору:

error CS9035: Required member 'Person.Name' must be set in the object initializer or attribute constructor.

Вы увидите один CS9035 на каждый неприсвоенный член required, поэтому тип с тремя свойствами required и пустым new Person() выдаёт три ошибки сразу. Это диагностика времени компиляции, а не исключение времени выполнения: сборка проваливается, ничего не запускается. В этом весь смысл required: проверка переместилась из NullReferenceException, на которое вы натыкались в продакшене, в красное подчёркивание, которое вы видите в редакторе.

Почему это происходит

Модификатор required, введённый в C# 11, помечает поле или свойство как обязательное на момент инициализации. Когда любое выражение конструирует новый экземпляр типа, компилятор проверяет, что каждый член required присвоен, либо через инициализатор объекта, либо через конструктор, который обещает их установить. Если он не может это доказать, он выдаёт CS9035.

Ключевое слово здесь — “доказать”. Компилятор не выполняет ваш код. Он доверяет только двум вещам: члену, который вы присваиваете напрямую в инициализаторе объекта, и конструктору, явно помеченному [SetsRequiredMembers]. Конструктор, который случайно присваивает член в своём теле, но не имеет атрибута, не считается ни за что; компилятор не станет читать тело, чтобы это выяснить. Именно поэтому ошибка сохраняется, даже когда ваш конструктор явно устанавливает значение: вы должны сказать это компилятору, а не показать ему.

Три ситуации порождают ошибку:

Минимальное воспроизведение

Наименьший тип, который вызывает CS9035:

// .NET 11, C# 14
public class Person
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public int Age { get; init; }   // not required, optional
}

Каждое из этих конструирований не компилируется:

// .NET 11, C# 14
var a = new Person();                        // CS9035 x2 (Name and Email)
var b = new Person { Name = "Ada" };         // CS9035 x1 (Email still missing)
var c = new Person { Age = 36 };             // CS9035 x2 (Name and Email)

Только когда оба члена required присутствуют, код компилируется:

// .NET 11, C# 14 -- compiles
var ok = new Person { Name = "Ada", Email = "ada@example.com" };

Обратите внимание, что Age опущено, и это нормально. required действует на каждый член в отдельности; необязательные члены остаются необязательными. Компилятору важно только то, чтобы члены, несущие модификатор required, были присвоены.

Исправление в деталях

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

1. Присвойте члены required в инициализаторе объекта

Предполагаемое исправление — установить каждый член required в месте вызова:

// .NET 11, C# 14
var person = new Person
{
    Name = "Ada Lovelace",
    Email = "ada@example.com",
    // Age is optional, omit it freely
};

Для этого required и существует: контракт типа говорит, что эти поля обязательны, и инициализатор это соблюдает. Если вы обнаруживаете, что повторяете одни и те же значения, это сигнал, что типу нужен конструктор, что является следующим исправлением.

2. Добавьте конструктор с атрибутом [SetsRequiredMembers]

Когда конструктор уже принимает значения и присваивает их, украсьте его атрибутом System.Diagnostics.CodeAnalysis.SetsRequiredMembers. Этот атрибут заверяет компилятор, что конструктор инициализирует каждый член required, поэтому вызывающему коду больше не нужен инициализатор объекта:

// .NET 11, C# 14
using System.Diagnostics.CodeAnalysis;

public class Person
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public int Age { get; init; }

    [SetsRequiredMembers]
    public Person(string name, string email)
    {
        Name = name;
        Email = email;
    }
}

// now this compiles, no initializer needed
var person = new Person("Ada", "ada@example.com");

Один острый угол: [SetsRequiredMembers] — это утверждение, а не проверенная гарантия. Компилятор верит вам на слово и не проверяет, что конструктор действительно присваивает каждый член required. Если вы добавите атрибут, но забудете установить Email в теле, вы не получите ни CS9035, ни предупреждения, только null там, где вы обещали значение. Держите атрибут честным.

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

3. Уберите required, если член на самом деле не обязателен

Если у члена есть разумное значение по умолчанию или он действительно необязателен, ему вообще не следует быть required. Удаление модификатора убирает обязательство:

// .NET 11, C# 14
public class Person
{
    public required string Name { get; init; }
    public string Email { get; init; } = "";   // was required, now optional with a default
}

Это правильное решение на удивление часто. Прибегайте к required только тогда, когда нет разумного значения по умолчанию и конструирование без значения оставило бы объект в недопустимом состоянии. Дать свойству значение по умолчанию и убрать required чище, чем заставлять каждое место вызова передавать пустую строку.

4. Для record используйте позиционный конструктор или конструктор с [SetsRequiredMembers]

Позиционный record генерирует одно свойство init на параметр, но они по умолчанию не required. Если вы явно добавляете свойство required к record с первичным конструктором, первичный конструктор не удовлетворяет его автоматически, и вы получаете CS9035, когда используете позиционную форму. Это сбивает людей с толку, потому что кажется, что конструктор должен считаться. Обоснование см. в обсуждении дизайна dotnet/csharplang #6780. Исправление — установить свойство в инициализаторе поверх позиционного вызова или добавить [SetsRequiredMembers] к конструктору, который его присваивает:

// .NET 11, C# 14
using System.Diagnostics.CodeAnalysis;

public record Product(string Sku)
{
    public required string Name { get; init; }

    [SetsRequiredMembers]
    public Product(string sku, string name) : this(sku) => Name = name;
}

// both work now
var withInit = new Product("A-100") { Name = "Widget" };
var withCtor = new Product("A-100", "Widget");

Если вы хотите, чтобы всё конструирование record обеспечивалось через позиционные параметры, предпочитайте простые свойства init вместо свойств required. Смешивание позиционных record и членов required — источник путаницы; решите, какую модель использует тип. Более широкий взгляд на то, когда record оправдан, см. в record vs class vs struct в C#.

Тонкости и варианты

Горстка ситуаций порождает CS9035 или нечто похожее по причинам, которые неочевидны из сообщения:

Ментальная модель, которую стоит удержать: required переносит правило “вы должны это предоставить” из надежды времени выполнения в гарантию времени компиляции, и CS9035 — это та гарантия, делающая свою работу. Когда вы её видите, исправление всегда одно из двух: “предоставьте значение в месте вызова” или “сообщите компилятору, что конструктор его уже предоставляет”. Решите, что верно для типа, затем заполните инициализатор или добавьте [SetsRequiredMembers], и никогда не прибегайте к атрибуту как к способу заглушить ошибку, не установив член на самом деле.

Похожее

Источники

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад