Как стать автором
Обновить

Комментарии 24

А какой смысл в SetsRequiredMembers если компилятор сам может эту проверку выполнить?

Кстати да, тоже непонятно. Но думается мне, что это для того чтобы убедится что поле точно инициализируется в хоть каком-то конструкторе. Сначала required объявили и если нет инициализации - "хоть тушкой, хоть чучелком" выкатываем ошибку. А [SetsRequiredMembers] явно указывает, что тут мы "сейчас будем играть во все игры". Раньше можно было warning'ами это отследить ("is null here") а теперь можно как ошибку засетапить. Наверное будет хорошо в REST запросах и так далее. А то там вечно бардак с параметрами...

НЛО прилетело и опубликовало эту надпись здесь

Ну в принципе да, просто перевод говно. Если компилятор не смог вывести факт инициализации то его можно зафорсить этим аттрибутом.

Хм… "Если компилятор не смог вывести факт инициализации то его можно зафорсить этим аттрибутом". Разве это не означает, что компилятор не смог вывести факт инициализации без атрибута, но атрибут явно указывает (заставляет компилятор увидеть), что члены есть и всё-таки "выводит факт их инициализации". То есть задаёт члены, задать которые было необходимо, а раньше он их вообще не видел. Во всяком случае, уточнили, спасибо.

В языке C# уже есть понятие Definite Assignment, к которому все привыкли. Можно было бы его переиспользовать для свойств.

на сколько я понял для Nullable там своя история и проверки эти не зависимые

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

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

Получается, что нужна.
Если класс Person (из статьи) объявлен в отдельной сборке, как компилятор поймёт без атрибута на конструкторе, что
new Person();

запрещена, а
new Person("John", "Smith");

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

У вас эта сборка не соберется если вы там наколбасите.

Почему же? Сам по себе конструктор без параметров имеет право на существование в отдельной сборке. Вызывать его надо так, как задумывалось при вводе в язык свойств с модификатором init:
new Person { FirstName = "John", LastName = "Smith" };
Непонятно. Тип, объявленный в сторонней библиотеке, не подлежит модификации. На конструктор мы не навесим атрибут, а поля не сможем пометить ключевым словом required.

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

А, вот в чём дело…

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

Мог бы. Тем не менее изначальный вопрос был «а нафига атрибут то?» и ваш вопрос на этот вопрос точно также отвечает

Нафига атрибут-то? Некоторые типовые (встроенные) атрибуты при компиляции исчезают - делается ли пометка флагом в специальных таблицах метаданных. То есть да - смысла в атрибуте после компиляции нет - в рефлексии он светиться не обязан (наоборот - только засорять всё будет). Напрямую на работу скомпилированных алгоритмов он не влияет. Нужна только спец пометка для компилятора при вызове такого кода. Да и то не 100% обязательная к выполнению (это уже на совести компилятора).

Кстати, может такая пометка и делается - и в рефлексии данного атрибута нет - надо будет проверить

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

Даже то что в с# коде не атрибуты зачастую является атрибутами в il. См. например learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.optionalattribute?view=net-6.0 или learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.extensionattribute?view=net-6.0 или даже learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.statemachineattribute?view=net-6.0

Кроме того, с# не единственный язык в .net экосистеме, и в эти языки далеко не всегда добавляют фичи из с#, а общаться друг с другом они должны. И делают это через атрибуты.

Ну в любом случае - это всё может делать исходный компилятор - не вынуждая программиста к явной пометке - а уже что в итоге будет в метаданных - спецфлаг или сгенерированный атрибут - не важно! Программиста напрямую это никак не должно касаться, и должно происходить в стороне, фоном.

Про "OptionalAttribute" не понял. В C# как раз не требуется помечать необязательные параметры методов этим атрибутом. Если Вы про то, что компилятор поставит его автоматически в метаданных у таки параметров - то ну и пусть - программиста то это делать не требуют. Вот и с обсуждаемой темой обязательных свойств и конструкторов полностью их инициализирующих - тут компилятор мог бы сам их поставить!

А вообще, я полностью на стороне атрибутов - со времён их первого появления в C# я просто влюбился в их идеологию. Жаль только, что

  1. В C# так мало они задействованы в типовых конструкциях - считаю, что нужно было поменьше вводить зарезервированных слов - и побольше встроенных атрибутов - сейчас зарезервированных слов наплодили просто немерено и изрядно усложнив язык и компиляторы/анализаторы. Вот то же ключевое слово "required" вполне можно было бы заменить атрибутом [Required]. Вот только синтаксис атрибутов не особо удачный (но и не самый плохой) - луше было бы так &Required или как аннотации в Java @Required - но амперсанд мне нравится больше, а собачка используется уже во множестве других языков. А вот ключевые слова доступности я бы вынес в атрибуты. И чтобы можно было связывать атрибуты в одну строку - как то так: &Required&Public&Static&Virtual void method.
    А если бы атрибуты ещё и по желанию можно было бы размещать справа от определения, а ещё и типы тоже.... эх.... это уже не C#.... увы даже и не совсем Kotlin - это уже оффтопик, за что прошу прощения.

  2. До Roslyn было мало толку в пользовательских атрибутах - их можно было анализировать только в рантайм через рефлексию - что снижало их популярность из-за снижения производительности (и даже runtime emit был не поноцеей - и при этом очень сложной технологией)! Но с приходом компилятора Roslyn, а за ним и системы когенерации (привет VS2022 и .NET 6 и .NET 7 - но можно и без них) и ряда библиотек таких как CodeAnalysis - ситуация изменилась в куда лучшую сторону - теперь на пользовательских атрибутах можно воротить невероятно продвинутый статически полиморфный код. И это надо активно пропагандировать. Вот тут как раз избыточные атрибуты могут оказаться очень полезны - это поможет кастомным анализаторам в анализе кода.

Об этом уже выше писали: не для каждого конструктора можно определить, какие поля он инициализирует (проблема останова).

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

А кто это за звери — «операторы вызова»?

that require callers to set certain properties

Угу, две загадки: как "звонящий" стал "оператором вызова" и куда вообще потерялись свойства.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий