Исправление: 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]. Конструктор, который случайно присваивает член в своём теле, но не имеет атрибута, не считается ни за что; компилятор не станет читать тело, чтобы это выяснить. Именно поэтому ошибка сохраняется, даже когда ваш конструктор явно устанавливает значение: вы должны сказать это компилятору, а не показать ему.
Три ситуации порождают ошибку:
- Вы вызываете
new T()илиnew T { ... }, не присвоив каждый членrequired. - Вы написали конструктор, который устанавливает члены required, но забыли
[SetsRequiredMembers], поэтому компилятор всё ещё требует инициализатор. - Вы добавили
requiredк члену базового типа, и путь конструирования производного типа больше его не удовлетворяет.
Минимальное воспроизведение
Наименьший тип, который вызывает 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 или нечто похожее по причинам, которые неочевидны из сообщения:
-
[SetsRequiredMembers]— это доверие, а не доказательство. Как отмечено выше, компилятор не проверяет конструктор. Конструктор с атрибутом, который пропускает член required, компилируется без замечаний и вручает вамnull. Относитесь к атрибуту как к контракту, за соблюдение которого отвечаете вы. -
System.Text.Json уважает
required. Начиная с .NET 8, десериализатор JSON обеспечивает члены required: если входящий JSON опускает свойство required, десериализация выбрасываетJsonExceptionво время выполнения вместо того, чтобы произвести наполовину собранный объект. Это ошибка времени выполнения, а неCS9035, но это тот же контракт, всплывающий на пути десериализации. Если вы видите сбой десериализации, упоминающий член required, значит в JSON отсутствует поле, которого требует тип. Об общей форме этой ошибки см. значение JSON не удалось преобразовать, а если вам нужна собственная логика конструирования, как написать пользовательский JsonConverter в System.Text.Json. Converter, который конструирует объект сам, обходит проверку членов required, поэтому это также запасной выход, когда тип, которым вы не владеете, сопротивляется вам. -
Рефлексия и
Activator.CreateInstanceполностью обходят проверку.requiredобеспечивается компилятором C#, а не средой выполнения.Activator.CreateInstance(typeof(Person))компилируется и выполняется, оставляя ссылочные члены required какnull. Если фреймворк конструирует ваши объекты через рефлексию, не учитывая[SetsRequiredMembers], вы можете получить “невозможный” объект, который компилятор никогда не позволил бы вам написать вручную. ORM и сериализаторы, материализующие объекты, — обычные подозреваемые; проверьте, поддерживают ли они члены required, прежде чем полагаться на модификатор для этих типов. Это особенно важно для сущностей, см. как правильно использовать record с EF Core 11. -
Наследование переносит обязательство вниз. Если у базового класса есть член
required, каждый производный тип также должен удовлетворять его при конструировании, и производный конструктор, который его не устанавливает, нуждается в[SetsRequiredMembers]или оставляет требование инициализатору вызывающего кода. Добавлениеrequiredк базовому члену — это ломающее исходный код изменение для всех конструкторов в иерархии, которые полагались на то, что член необязателен. -
initпротивsetдляrequiredне имеет значения.requiredортогонален доступности сеттера. Вы можете пометить членrequiredчерезinit(устанавливается только во время конструирования) или через обычныйset. Модификатор управляет тем, должен ли член быть присвоен; аксессор управляет тем, когда он может быть присвоен. Распространённый шаблон —public required string Name { get; init; }: обязателен к установке, и только при конструировании. -
Член должен быть по крайней мере настолько же доступен, как тип. Член
requiredдолжен быть устанавливаемым любым кодом, который может сконструировать тип. Типpublicс членомrequired, у которого аксессор init имеет доступinternal, порождает другую диагностику (CS9032/CS9033), потому что внешний вызывающий код мог бы сконструировать тип, но не удовлетворить его требование. Если вы сужаете аксессор члена required, сузьте также конструктор или тип.
Ментальная модель, которую стоит удержать: required переносит правило “вы должны это предоставить” из надежды времени выполнения в гарантию времени компиляции, и CS9035 — это та гарантия, делающая свою работу. Когда вы её видите, исправление всегда одно из двух: “предоставьте значение в месте вызова” или “сообщите компилятору, что конструктор его уже предоставляет”. Решите, что верно для типа, затем заполните инициализатор или добавьте [SetsRequiredMembers], и никогда не прибегайте к атрибуту как к способу заглушить ошибку, не установив член на самом деле.
Похожее
- record vs class vs struct в C#: матрица решений для выбора правильной формы типа, прежде чем рассыпать по нему
required. - Как правильно использовать record с EF Core 11 о том, как члены required взаимодействуют с сущностями, которые ORM материализует через рефлексию.
- Как написать пользовательский JsonConverter в System.Text.Json, чтобы взять конструирование под контроль, когда сериализатор по умолчанию борется с вашими членами required.
- Исправление: значение JSON не удалось преобразовать для стороны времени выполнения того же контракта при десериализации.
- Как объявить свойства-расширения в C# 14 о том, что модель свойств C# 14 может и не может выразить.
Источники
- Microsoft Learn, модификатор required (справочник C#) (семантика
required, атрибутSetsRequiredMembersи правило, что компилятор не проверяет тело конструктора). - Microsoft Learn, Инициализаторы объектов и коллекций (как члены required должны устанавливаться через инициализатор или конструктор атрибута).
- GitHub, dotnet/csharplang Discussion #6780 (почему
CS9035возникает для record, сочетающих первичный конструктор с явными свойствамиrequired).
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.