Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
23.1 Общие
Большая часть языка C# позволяет программисту указывать декларативную информацию о сущностях, определенных в программе. Например, специальные возможности метода в классе задаются путем декорирования с помощью method_modifier, publicprotectedinternalи .private
C# позволяет программистам изобретать новые типы декларативной информации, называемые атрибутами. Затем программисты могут присоединять атрибуты к различным сущностям программы и получать сведения о атрибутах в среде выполнения.
Примечание. Например, платформа может определить
HelpAttributeатрибут, который можно поместить на определенные элементы программы (например, классы и методы), чтобы обеспечить сопоставление этих элементов программы с их документацией. конечная заметка
Атрибуты определяются посредством объявления классов атрибутов (§23.2), которые могут иметь позиционные и именованные параметры (§23.2.3). Атрибуты присоединены к сущностям в программе C# с помощью спецификаций атрибутов (§23.3) и могут быть получены во время выполнения как экземпляры атрибутов (§23.4).
Классы атрибутов 23.2
23.2.1 Общие
Класс, производный от абстрактного класса System.Attribute, напрямую или косвенно, является классом атрибута. Объявление класса атрибута определяет новый вид атрибута, который можно поместить в сущности программы. По соглашению классы атрибутов называются суффиксом Attribute. Использование атрибута может включать или пропускать этот суффикс.
Объявление универсального класса не должно использоваться System.Attribute в качестве прямого или косвенного базового класса.
Пример:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attributeпример конца
Использование атрибутов 23.2.2
Атрибут AttributeUsage (§23.5.2) используется для описания использования класса атрибутов.
AttributeUsage имеет позиционный параметр (§23.2.3), который позволяет классу атрибутов указывать типы сущностей программы, для которых ее можно использовать.
Пример. В следующем примере определяется класс атрибута с именем
SimpleAttribute, который можно поместить только на class_declaration и interface_declarationи показывает несколько вариантов использования атрибутаSimple.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}Хотя этот атрибут определен с именем
SimpleAttribute,Attributeпри использовании этого атрибута суффикс может быть опущен, что приводит к короткому имениSimple. Таким образом, приведенный выше пример семантически эквивалентен следующему.[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}пример конца
AttributeUsage имеет именованный параметр (§23.2.3), который AllowMultipleуказывает, может ли атрибут быть указан несколько раз для данной сущности. Если AllowMultiple для класса атрибута задано значение true, то этот класс атрибута является классом атрибутов с несколькими использованием и может быть указан несколько раз в сущности. Если AllowMultiple для класса атрибута задано значение false или оно не указано, то этот класс атрибута является классом атрибутов с одним использованием и может быть указан не более одного раза в сущности.
Пример. В следующем примере определяется класс атрибутов с несколькими
AuthorAttributeиспользованием и отображается объявление класса с двумя использованием атрибутаAuthor:[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) => Name = name; } [Author("Brian Kernighan"), Author("Dennis Ritchie")] class Class1 { ... }пример конца
AttributeUsage имеет другой именованный параметр (§23.2.3), который Inheritedуказывает, является ли атрибут, указанный в базовом классе, также наследуется классами, производными от этого базового класса. Если Inherited для класса атрибута задано значение true, этот атрибут наследуется. Если Inherited для класса атрибута задано значение false, то этот атрибут не наследуется. Если оно не указано, значение по умолчанию имеет значение true.
Класс X атрибута, не подключенный AttributeUsage к нему, как в
class X : Attribute { ... }
эквивалентен следующему:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
23.2.3 Позиционные и именованные параметры
Классы атрибутов могут иметь позиционные параметрыи именованные параметры. Каждый конструктор общедоступного экземпляра для класса атрибутов определяет допустимую последовательность позиционных параметров для этого класса атрибута. Каждое нестатическое открытое поле чтения и записи для класса атрибутов определяет именованный параметр для класса атрибутов. Для определения именованного параметра свойство должно иметь как общедоступный метод доступа, так и общедоступный метод доступа к набору.
Пример. В следующем примере определяется класс атрибутов с именем
HelpAttributeодного позиционного параметра,urlа также один именованный параметрTopic. Хотя это нестатическое и общедоступное, свойствоUrlне определяет именованный параметр, так как он не является чтением и записью. Также показаны два использования этого атрибута:[AttributeUsage(AttributeTargets.Class)] public class HelpAttribute : Attribute { public HelpAttribute(string url) // url is a positional parameter { ... } // Topic is a named parameter public string Topic { get; set; } public string Url { get; } } [Help("http://www.mycompany.com/xxx/Class1.htm")] class Class1 { } [Help("http://www.mycompany.com/xxx/Misc.htm", Topic ="Class2")] class Class2 { }пример конца
Типы параметров атрибута 23.2.4
Типы позиционных и именованных параметров для класса атрибутов ограничены типами параметров атрибута, которые:
- Один из следующих типов:
bool,bytechardoublefloatintlongsbyteshortstringuintulongushort - Тип
object. - Тип
System.Type. - Типы перечисления.
- Одномерные массивы указанных выше типов.
- Аргумент конструктора или общедоступное поле, которое не имеет одного из этих типов, не должно использоваться в качестве позиционного или именованного параметра в спецификации атрибута.
Спецификация атрибута 23.3
Применение ранее определенного атрибута к сущности программы называется спецификацией атрибутов. Атрибут — это часть дополнительных декларативных сведений, указанных для сущности программы. Атрибуты можно указать в глобальной области (чтобы указать атрибуты в содержащей сборке или модуле) и для type_declarations (§14.7), class_member_declarations (§15.3), interface_member_declarations (§19.0) 4), struct_member_declarations (§16.3), enum_member_declarations (§20.2), accessor_declarations (§15.7.3), event_accessor_declarations (§15.8)), элементы parameter_list(§15.6.2) и элементы type_parameter_list(§15.2.3).
Атрибуты указываются в разделах атрибутов. Раздел атрибута состоит из пары квадратных квадратных скобок, которые окружают разделенный запятыми список одного или нескольких атрибутов. Порядок, в котором атрибуты указаны в таком списке, и порядок, в котором разделы, присоединенные к той же сущности программы, не являются значительными. Например, спецификации атрибутов [A][B], [B][A][A, B]и [B, A] эквивалентны.
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' global_attribute_target_specifier attribute_list ']'
;
global_attribute_target_specifier
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)* ','?
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
Для рабочей global_attribute_target, а в приведенном ниже тексте идентификатор должен иметь правописание assembly или module, где равенство определяется в §6.4.3. Для рабочей attribute_target и в приведенном ниже тексте идентификатор должен иметь орфографию, которая не равна assembly или moduleиспользует то же определение равенства, что и выше.
Атрибут состоит из attribute_name и необязательного списка позиционных и именованных аргументов. Позиционные аргументы (если они есть) предшествуют именованным аргументам. Позиционный аргумент состоит из attribute_argument_expression; именованный аргумент состоит из имени, за которым следует знак равенства, а затем attribute_argument_expression, который вместе ограничивается теми же правилами, что и простое назначение. Порядок именованных аргументов не является значительным.
Примечание. Для удобства конечная запятая разрешена в global_attribute_section и attribute_section, так же как и в array_initializer (§17.7). конечная заметка
Attribute_name определяет класс атрибутов.
При размещении атрибута на глобальном уровне требуется global_attribute_target_specifier . Если global_attribute_target равно:
-
assembly— целевой объект — это содержащая сборка -
module— целевой объект — это содержащий модуль
Другие значения для global_attribute_target не допускаются.
Стандартизированные attribute_target имена: event, field, method, paramproperty, , returnи typetypevar. Эти имена целевых объектов должны использоваться только в следующих контекстах:
-
event— событие. -
field— поле. Событие типа поля (т. е. одно без методов доступа) (§15.8.2) и автоматически реализованное свойство (§15.7.4) также может иметь атрибут с этим целевым объектом. -
method— конструктор, метод, метод, оператор, получение и установка методов доступа, индексатор получает и устанавливает методы доступа, а также добавляет и удаляет методы доступа. Событие типа поля (т. е. одно без методов доступа) также может иметь атрибут с этим целевым объектом. -
param— метод доступа набора свойств, метод и оператор набора индексаторов, добавление и удаление методов доступа, а также параметр в конструкторе, методе и операторе. -
property— свойство и индексатор. -
return— делегат, метод, оператор, метод получения доступа к свойству и метод доступа индексатора. -
type— делегат, класс, структура, перечисление и интерфейс. -
typevar— параметр типа.
Некоторые контексты позволяют спецификации атрибута в нескольких целевых объектах. Программа может явно указать целевой объект, включив attribute_target_specifier. Без attribute_target_specifier применяется значение по умолчанию, но attribute_target_specifier можно использовать для подтверждения или переопределения по умолчанию. Контексты разрешаются следующим образом:
- Для атрибута в объявлении делегата целевой объект по умолчанию является делегатом. В противном случае, если attribute_target равен:
-
type— целевой объект — делегат -
return— целевой объект — возвращаемое значение
-
- Для атрибута в объявлении метода целевой объект по умолчанию — это метод. В противном случае, если attribute_target равен:
-
method— целевой объект — это метод. -
return— целевой объект — возвращаемое значение
-
- Для атрибута в объявлении оператора целевой объект по умолчанию является оператором. В противном случае, если attribute_target равен:
-
method— целевой объект — оператор -
return— целевой объект — возвращаемое значение
-
- Для атрибута в объявлении метода доступа для свойства или объявления индексатора целевой объект по умолчанию является соответствующим методом. В противном случае, если attribute_target равен:
-
method— целевой объект — связанный метод. -
return— целевой объект — возвращаемое значение
-
- Для атрибута, указанного в методе доступа набора для свойства или объявления индексатора, целевой объект по умолчанию является соответствующим методом. В противном случае, если attribute_target равен:
-
method— целевой объект — связанный метод. -
param— целевой объект является неявным параметром
-
- Для атрибута в автоматическом объявлении свойств целевой объект по умолчанию является свойством. В противном случае, если attribute_target равен:
-
field— целевой объект — это поле резервного копирования, созданное компилятором для свойства.
-
- Для атрибута, указанного в объявлении события, которое не event_accessor_declarations целевой объект по умолчанию является объявлением события. В противном случае, если attribute_target равен:
-
event— целевой объект — объявление события -
field— целевой объект — это поле -
method— целевыми объектами являются методы
-
- В случае объявления события, которое не пропускает event_accessor_declarations целевой объект по умолчанию является методом.
-
method— целевой объект — связанный метод. -
param— целевой объект — это один параметр
-
Во всех других контекстах включение attribute_target_specifier допускается, но ненужно.
Пример: объявление класса может включать или опустить описатель
type:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}пример конца.
Реализация может принимать другие attribute_target, для которых определены цели реализации. Реализация, которая не распознает такой attribute_target , должна выдавать предупреждение и игнорировать содержащий attribute_section.
По соглашению классы атрибутов называются суффиксом Attribute.
Attribute_name может включать или опустить этот суффикс. В частности, attribute_name разрешается следующим образом:
- Если правильный идентификатор attribute_name является подробным идентификатором (§6.4.3), то attribute_name разрешается как type_name (§7.8). Если результат не является типом, производным от
System.Attribute, возникает ошибка во время компиляции. - В противном случае — значение .
-
Attribute_name разрешается как type_name (§7.8), за исключением всех ошибок. Если это разрешение выполнено успешно и приводит к получению типа,
System.Attributeто тип является результатом этого шага. - Символы добавляются к правому идентификатору в attribute_name, а результирующая строка маркеров
Attributeразрешается как type_name (§7.8), за исключением ошибок. Если это разрешение выполнено успешно и приводит к получению типа,System.Attributeто тип является результатом этого шага.
-
Attribute_name разрешается как type_name (§7.8), за исключением всех ошибок. Если это разрешение выполнено успешно и приводит к получению типа,
Если именно один из двух шагов выше приводит к типу, производным от System.Attributeэтого типа, то этот тип является результатом attribute_name. В противном случае возникает ошибка во время компиляции.
Пример. Если класс атрибута найден как с суффиксом, так и без этого суффикса, присутствует неоднозначность и результаты ошибки во время компиляции. Если attribute_name орфографический, чтобы его правильный идентификатор был подробным идентификатором (§6.4.3), то сопоставляется только атрибут без суффикса, что позволяет разрешить такую неоднозначность. Пример
[AttributeUsage(AttributeTargets.All)] public class Example : Attribute {} [AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Error: ambiguity class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Refers to Example class Class3 {} [@ExampleAttribute] // Refers to ExampleAttribute class Class4 {}показывает два класса атрибутов с именем
ExampleиExampleAttribute. Атрибут[Example]неоднозначный, так как он может ссылаться на любойExampleилиExampleAttribute. Использование подробного идентификатора позволяет указать точное намерение в таких редких случаях. Атрибут[ExampleAttribute]не является неоднозначным (хотя это было бы, если бы существовал класс атрибутов с именемExampleAttributeAttribute!). Если объявление для классаExampleудалено, оба атрибута ссылаются на класс атрибутов с именемExampleAttributeследующим образом:[AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Refers to ExampleAttribute class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Error: no attribute named “Example” class Class3 {}пример конца
Это ошибка во время компиляции для использования класса атрибутов с одним использованием более одного раза в одной сущности.
Пример: пример
[AttributeUsage(AttributeTargets.Class)] public class HelpStringAttribute : Attribute { public HelpStringAttribute(string value) { Value = value; } public string Value { get; } } [HelpString("Description of Class1")] [HelpString("Another description of Class1")] // multiple uses not allowed public class Class1 {}приводит к ошибке во время компиляции, так как она пытается использовать
HelpString, который является классом атрибутов с одним использованием, несколько раз в объявленииClass1.пример конца
Выражение E является attribute_argument_expression , если все следующие операторы имеют значение true:
- Тип параметра атрибута
E(§23.2.4). - Во время компиляции
Eможно разрешить одно из следующих значений:- Значение константы .
-
System.TypeОбъект, полученный с помощью typeof_expression (§12.8.18), указывающий не универсальный тип, закрытый созданный тип (§8.4.3) или несвязанный универсальный тип (§8.4.4), но не открытый тип (§8.4.3). - Одномерный массив attribute_argument_expression.
Пример:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)] public class TestAttribute : Attribute { public int P1 { get; set; } public Type P2 { get; set; } public object P3 { get; set; } } [Test(P1 = 1234, P3 = new int[]{1, 3, 5}, P2 = typeof(float))] class MyClass {} class C<T> { [Test(P2 = typeof(T))] // Error – T not a closed type. int x1; [Test(P2 = typeof(C<T>))] // Error – C<;T>; not a closed type. int x2; [Test(P2 = typeof(C<int>))] // Ok int x3; [Test(P2 = typeof(C<>))] // Ok int x4; }пример конца
Атрибуты типа, объявленного в нескольких частях, определяются объединением атрибутов каждого из его частей в неопределенном порядке. Если один и тот же атрибут помещается на несколько частей, он эквивалентен указанию этого атрибута несколько раз в типе.
Пример: две части:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}эквивалентны следующему одному объявлению:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}пример конца
Атрибуты для параметров типа объединяются так же.
23.4 Экземпляры атрибутов
23.4.1 Общие
Экземпляр атрибута — это экземпляр , представляющий атрибут во время выполнения. Атрибут определяется классом атрибута, позициальными аргументами и именованными аргументами. Экземпляр атрибута — это экземпляр класса атрибута, который инициализирован с позициальными и именованными аргументами.
Получение экземпляра атрибута включает как обработку во время компиляции, так и во время выполнения, как описано в следующих подклаузах.
23.4.2 Компиляция атрибута
Компиляция атрибута с классом атрибутов T , , P и указана в сущности программы, компилируется в сборку N с помощью следующих шагов:
- Выполните действия по обработке во время компиляции для компиляции object_creation_expression новой
T(P)формы. Эти шаги либо приводят к ошибке во время компиляции, либо определяют конструкторCTэкземпляра, который можно вызвать во время выполнения. - Если
Cнет общедоступной доступности, возникает ошибка во время компиляции. - Для каждого named_argument
ArgвN:- Давайте будем
Nameидентификаторомnamed_argumentArg. -
Nameдолжен определять общедоступное поле или свойство, не статическое чтение и запись.TЕслиTтакого поля или свойства нет, возникает ошибка во время компиляции.
- Давайте будем
- Если какое-либо из значений в positional_argument_list
Pили одно из значений в named_argument_listNимеет типSystem.String, и значение не является хорошо сформированным, как определено стандартом Юникода, оно определяется реализацией, равно ли скомпилированному значению времени выполнения (§23.4.3).Примечание. В качестве примера строка, содержащая высоко суррогатную единицу кода UTF-16, за которой не следует единица кода с низким уровнем суррогата, не является хорошо сформированной. конечная заметка
- Сохраните следующие сведения (для создания экземпляра атрибута во время выполнения) в выходных данных сборки компилятором в результате компиляции программы, содержащей атрибут: класс
Tатрибута, конструкторCэкземпляра вT,Pи связанную сущностьNпрограммы, со значениями, разрешаемыми полностью во время компиляции.
23.4.3 Извлечение экземпляра атрибута во время выполнения
Используя термины, определенные в §23.4.2, экземпляр атрибута, представленный T, CPи, а Nтакже связанный с E ним, можно получить во время выполнения из сборкиA, выполнив следующие действия:
- Выполните действия по обработке во время выполнения для выполнения object_creation_expression формы
new T(P)с помощью конструктораCэкземпляра и значений, определенных во время компиляции. Эти действия либо приводят к исключению, либо создают экземплярOT. - Для каждого named_argument
ArgвNпорядке:- Давайте будем
Nameидентификаторомnamed_argumentArg. ЕслиNameне определяет нестатическое открытое поле записи илиOсвойство, возникает исключение. - Давайте будем
Valueрезультатом оценки attribute_argument_expressionArg. - Если
Nameидентифицирует полеOв, задайте для этого поля значениеValue. - В противном случае имя идентифицирует свойство в
O. Задайте для этого свойства значение Value. - Результатом является
Oэкземпляр классаTатрибута, который был инициализирован с помощью positional_argument_listPи named_argument_listN.
- Давайте будем
Примечание. Формат хранения
T,C,PN(и связывания его сE)Aи механизм для указанияEи извлеченияTCPN, изA(и, следовательно, как экземпляр атрибута получен во время выполнения) выходит за рамки этой спецификации. конечная заметка
23.5 Зарезервированные атрибуты
23.5.1 Общие
Ряд атрибутов влияет на язык каким-то образом. Они перечислены ниже:
-
System.AttributeUsageAttribute(§23.5.2), который используется для описания способов использования класса атрибутов. -
System.Diagnostics.ConditionalAttribute(§23.5.3) — это класс атрибутов с несколькими использованием, который используется для определения условных методов и классов условных атрибутов. Этот атрибут указывает условие путем тестирования символа условной компиляции. -
System.ObsoleteAttribute(§23.5.4), который используется для обозначения элемента как устаревшего. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5), который используется для создания построителя задач для асинхронного метода. -
System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3) иSystem.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4), которые используются для предоставления сведений о контексте вызова необязательным параметрам. -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8), который используется для указания параметра маркера отмены в асинхронном итераторе.
Атрибуты статического анализа, допускающие значение NULL (§23.5.7), могут улучшить правильность предупреждений, созданных для значений NULL и состояний NULL (§8.9.5).
Среда выполнения может предоставлять дополнительные атрибуты, определенные реализацией, влияющие на выполнение программы C#.
23.5.2 АтрибутUsage
AttributeUsage Атрибут используется для описания способа использования класса атрибута.
Класс, украшенный AttributeUsage атрибутом, должен быть производным от System.Attributeпрямого или косвенного. В противном случае возникает ошибка во время компиляции.
Примечание. Пример использования этого атрибута см. в разделе §23.2.2. конечная заметка
23.5.3 Условный атрибут
23.5.3.1 Общие
Conditional Атрибут включает определение классов условных методови условных атрибутов.
Условные методы 23.5.3.2
Метод, Conditional украшенный атрибутом, является условным методом. Таким образом, каждый условный метод связан с символами условной компиляции, объявленными в его Conditional атрибутах.
Пример:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }
Eg.Mобъявляется как условный метод, связанный с двумя символами условной компиляцииALPHAиBETA.пример конца
Вызов условного метода включается, если один или несколько связанных символов условной компиляции определены в точке вызова, в противном случае вызов опущен.
Условный метод применяется к следующим ограничениям:
- Условный метод должен быть методом в class_declaration или struct_declaration. Ошибка во время компиляции возникает, если
Conditionalатрибут указан в методе в объявлении интерфейса. - Условный метод не должен быть методом доступа к свойству, индексатору или событию.
- Условный метод должен иметь тип возвращаемого
voidзначения. - Условный метод не должен быть помечен модификатором
override. Условный метод можно пометить модификаторомvirtual, однако. Переопределения такого метода неявно условны и не должны быть явно помечены атрибутомConditional. - Условный метод не должен быть реализацией метода интерфейса. В противном случае возникает ошибка во время компиляции.
- Параметры условного метода не должны быть выходными параметрами.
Примечание. Атрибуты с
AttributeUsageатрибутами (§23.2.2), включаяAttributeTargets.Methodобычно могут применяться к методу доступа свойств, индексаторов и событий. Указанные выше ограничения запрещают использование этого атрибутаConditional. конечная заметка
Кроме того, ошибка во время компиляции возникает, если делегат создается из условного метода.
Пример: пример
#define DEBUG using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void M() { Console.WriteLine("Executed Class1.M"); } } class Class2 { public static void Test() { Class1.M(); } }
Class1.Mобъявляется как условный метод.Class2TestМетод вызывает этот метод. Так как символDEBUGусловной компиляции определен, еслиClass2.Testвызывается, он вызоветM. Если символDEBUGне определен,Class2.Testто не будет вызыватьсяClass1.M.пример конца
Важно понимать, что включение или исключение вызова условного метода управляется символами условной компиляции в точке вызова.
Пример. В следующем коде
// File Class1.cs: using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void F() { Console.WriteLine("Executed Class1.F"); } } // File Class2.cs: #define DEBUG class Class2 { public static void G() { Class1.F(); // F is called } } // File Class3.cs: #undef DEBUG class Class3 { public static void H() { Class1.F(); // F is not called } }классы
Class2иClass3каждый из них содержат вызовы условного методаClass1.F, который является условным в зависимости от того, определен ли онDEBUG. Так как этот символ определен в контекстеClass2, но неClass3включается вызов, в то время как вызовFClass2FClass3включено.пример конца
Использование условных методов в цепочке наследования может быть запутанным. Вызовы условного метода с помощью baseформы base.Mприменяются к обычным правилам вызова условного метода.
Пример. В следующем коде
// File Class1.cs using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public virtual void M() => Console.WriteLine("Class1.M executed"); } // File Class2.cs class Class2 : Class1 { public override void M() { Console.WriteLine("Class2.M executed"); base.M(); // base.M is not called! } } // File Class3.cs #define DEBUG class Class3 { public static void Main() { Class2 c = new Class2(); c.M(); // M is called } }
Class2включает вызов определенного в базовомMклассе. Этот вызов опущен, так как базовый метод является условным на основе наличия символаDEBUG, который не определен. Таким образом, метод записывает только в консоль "Class2.M executed". Разумное использование pp_declarationможет устранить такие проблемы.пример конца
Классы условных атрибутов 23.5.3
Класс атрибута (§23.2), украшенный одним или несколькими Conditional атрибутами, является классом условного атрибута. Таким образом, класс условного атрибута связан с символами условной компиляции, объявленными в его Conditional атрибутах.
Пример:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttributeобъявляется как класс условного атрибута, связанный с символами условной компиляцииALPHAиBETA.пример конца
Спецификации атрибутов (§23.3) условного атрибута включаются, если один или несколько связанных символов условной компиляции определены в точке спецификации, в противном случае спецификация атрибута опущена.
Важно отметить, что включение или исключение спецификации атрибута класса условного атрибута контролируется символами условной компиляции в точке спецификации.
Пример. В примере
// File Test.cs: using System; using System.Diagnostics; [Conditional("DEBUG")] public class TestAttribute : Attribute {} // File Class1.cs: #define DEBUG [Test] // TestAttribute is specified class Class1 {} // File Class2.cs: #undef DEBUG [Test] // TestAttribute is not specified class Class2 {}классы
Class1иClass2каждый из них украшен атрибутомTest, который является условным в зависимости от того, определен ли онDEBUG. Так как этот символ определен в контекстеClass1, но нетClass2, спецификация атрибутаClass1Test включена, а спецификацияTestатрибутаClass2опущена.пример конца
23.5.4 Устаревший атрибут
Obsolete Атрибут используется для маркировки типов и элементов типов, которые больше не должны использоваться.
Если программа использует тип или элемент, украшенный атрибутом Obsolete, компилятор должен выдавать предупреждение или ошибку. В частности, компилятор должен выдавать предупреждение, если параметр ошибки не указан, или если параметр ошибки указан и имеет значение false. Компилятор должен выдавать ошибку, если указан параметр ошибки и имеет значение true.
Пример. В следующем коде
[Obsolete("This class is obsolete; use class B instead")] class A { public void F() {} } class B { public void F() {} } class Test { static void Main() { A a = new A(); // Warning a.F(); } }Класс
Aукрашен атрибутомObsolete. Каждое использование приводит к предупреждениюAMain, включающее указанное сообщение: "Этот класс устарел; вместо этого используйте классB".пример конца
23.5.5 Атрибут AsyncMethodBuilder
Этот атрибут описан в разделе §15.14.1.
Атрибуты caller-info 23.5.6
23.5.6.1 Общие
В таких целях, как ведение журнала и отчеты, иногда полезно для члена функции получить определенные сведения о коде вызова во время компиляции. Атрибуты сведений о вызывающем объекте предоставляют способ прозрачного передачи таких сведений.
Если необязательный параметр аннотирован с одним из атрибутов сведений о вызывающем объекте, опущение соответствующего аргумента в вызове не обязательно приводит к замене значения параметра по умолчанию. Вместо этого, если доступны указанные сведения о контексте вызова, эти сведения будут переданы в качестве значения аргумента.
Пример:
public void Log( [CallerLineNumber] int line = -1, [CallerFilePath] string path = null, [CallerMemberName] string name = null ) { Console.WriteLine((line < 0) ? "No line" : "Line "+ line); Console.WriteLine((path == null) ? "No file path" : path); Console.WriteLine((name == null) ? "No member name" : name); }Вызов
Log()без аргументов печатает номер строки и путь к файлу вызова, а также имя члена, в котором произошел вызов.пример конца
Атрибуты caller-info могут выполняться в любом месте необязательных параметров, включая объявления делегатов. Однако конкретные атрибуты сведений о вызывающем объекте имеют ограничения на типы параметров, которые они могут атрибутировать, поэтому всегда будет неявное преобразование из заменяемого значения в тип параметра.
Это ошибка иметь один и тот же атрибут caller-info в параметре определения и реализации части объявления частичного метода. Применяются только атрибуты caller-info в определяющей части, а атрибуты caller-info, происходящие только в реализующей части.
Сведения о вызывающем объекте не влияют на разрешение перегрузки. Поскольку необязательные параметры атрибута по-прежнему опущены из исходного кода вызывающего объекта, разрешение перегрузки игнорирует эти параметры так же, как и другие пропущенные необязательные параметры (§12.6.4).
Сведения о вызывающем объекте заменяются только при явном вызове функции в исходном коде. Неявные вызовы, такие как неявные вызовы родительского конструктора, не имеют исходного расположения и не заменят сведения вызывающего объекта. Кроме того, вызовы, которые динамически привязаны, не заменяют сведения вызывающего абонента. Если параметр атрибута caller-info опущен в таких случаях, вместо этого используется указанное значение по умолчанию параметра.
Одним из исключений является выражение запросов. Они считаются синтаксическими расширениями, и если вызовы, которые они расширяют, чтобы опустить необязательные параметры с атрибутами caller-info, будут заменены сведения о вызывающем объекте. Используемое расположение — это расположение предложения запроса, из которого был создан вызов.
Если для заданного параметра задано несколько атрибутов caller-info, они распознаются в следующем порядке: CallerLineNumber, CallerFilePath. CallerMemberName Рассмотрим следующее объявление параметра:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber имеет приоритет, а другие два атрибута игнорируются. Если CallerLineNumber бы не было опущено, CallerFilePath будет иметь приоритет и CallerMemberName будет игнорироваться. Лексическое упорядочение этих атрибутов не имеет значения.
23.5.6.2 Атрибут CallerLineNumber
Атрибут System.Runtime.CompilerServices.CallerLineNumberAttribute допускается для необязательных параметров, если существует стандартное неявное преобразование (§10.4.2) из константного значения int.MaxValue в тип параметра. Это гарантирует, что любое не отрицательное число строки до этого значения может быть передано без ошибок.
Если вызов функции из расположения в исходном коде пропускает необязательный параметр с CallerLineNumberAttributeпомощью, то числовый литерал, представляющий номер строки расположения, используется в качестве аргумента для вызова вместо значения параметра по умолчанию.
Если вызов охватывает несколько строк, выбранная строка зависит от реализации.
Номер строки может повлиять на #line директивы (§6.5.8).
23.5.6.3 Атрибут CallerFilePath
Атрибут System.Runtime.CompilerServices.CallerFilePathAttribute допускается для необязательных параметров, если существует стандартное неявное преобразование (§10.4.2) в string тип параметра.
Если вызов функции из расположения в исходном коде пропускает необязательный параметр с CallerFilePathAttributeпомощью, строковый литерал, представляющий путь к файлу расположения, используется в качестве аргумента для вызова вместо значения параметра по умолчанию.
Формат пути к файлу зависит от реализации.
Путь к файлу может повлиять на #line директивы (§6.5.8).
23.5.6.4 Атрибут CallerMemberName
Атрибут System.Runtime.CompilerServices.CallerMemberNameAttribute допускается для необязательных параметров, если существует стандартное неявное преобразование (§10.4.2) в string тип параметра.
Если вызов функции из расположения элемента функции или атрибута, примененного к самому элементу функции, или к его типу возвращаемого типа, параметрам или параметрам типа в исходном коде не следует указывать необязательный параметр, CallerMemberNameAttributeстроковый литерал, представляющий имя этого элемента, используется в качестве аргумента для вызова вместо значения параметра по умолчанию.
Для вызовов, происходящих в универсальных методах, используется только имя метода без списка параметров типа.
Для вызовов, происходящих в явных реализациях элементов интерфейса, используется только имя метода без предыдущей квалификации интерфейса.
Для вызовов, происходящих в свойствах или методах доступа к событиям, имя члена используется для самого свойства или события.
Для вызовов, происходящих в методах доступа индексатора, используется имя члена, предоставленное элементом IndexerNameAttribute (§23.6) в члене индексатора, если он присутствует, или имя Item по умолчанию.
Для вызовов, происходящих в полях или инициализаторах событий, имя члена, используемое, — это имя поля или события, инициализированного события.
Для вызовов, происходящих в объявлениях конструкторов экземпляров, статических конструкторов, завершающих и операторов, используемых именем члена, зависит от реализации.
Атрибуты анализа кода 23.5.7
23.5.7.1 Общие
Атрибуты в этом подклаузе используются для предоставления дополнительных сведений в помощь компилятору, обеспечивающему диагностику наличия null и нулевого состояния (§8.9.5). Компилятор не требуется для выполнения каких-либо диагностика состояния NULL. Наличие или отсутствие этих атрибутов не влияет на язык или поведение программы. Компилятор, который не предоставляет диагностика состояния NULL, должен считывать и игнорировать наличие этих атрибутов. Компилятор, предоставляющий диагностику состояния NULL, должен использовать значение, определенное в этом подклаузе для любого из этих атрибутов, используемых для информирования о его диагностике.
Атрибуты анализа кода объявляются в пространстве System.Diagnostics.CodeAnalysisимен.
| Атрибут | Значение |
|---|---|
AllowNull (§23.5.7.2) |
Аргумент, не допускающий значение NULL, может принимать значение NULL. |
DisallowNull (§23.5.7.3) |
Аргумент, допускающий значение NULL, никогда не должен принимать значение NULL. |
MaybeNull (§23.5.7.6) |
Возвращаемое значение, не допускающее значение NULL, может быть равно NULL. |
NotNull (§23.5.7.8) |
Возвращаемое значение, допускающее значение NULL, никогда не будет равно NULL. |
MaybeNullWhen (§23.5.7.7) |
Аргумент, не допускающий значение NULL, может иметь значение NULL, если метод возвращает указанное значение bool. |
NotNullWhen (§23.5.7.10) |
Аргумент, допускающий значение NULL, не будет иметь значение NULL, если метод возвращает указанное bool значение. |
NotNullIfNotNull (§23.5.7.9) |
Возвращаемое значение не равно NULL, если аргумент для указанного параметра не имеет значения NULL. |
DoesNotReturn (§23.5.7.4) |
Этот метод никогда не возвращает значение . |
DoesNotReturnIf (§23.5.7.5) |
Этот метод никогда не возвращает значение, если связанный параметр bool имеет указанное значение. |
Следующие подклаузы в §23.5.7.1 являются условно нормативными.
23.5.7.2 Атрибут AllowNull
Указывает, что значение NULL разрешено в качестве входных данных, даже если соответствующий тип запрещает его.
Пример. Рассмотрим следующее свойство чтения и записи, которое никогда не возвращается
null, так как оно имеет разумное значение по умолчанию. Однако пользователь может предоставить пользователю значение NULL для метода доступа к набору, чтобы задать для свойства это значение по умолчанию.#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }Учитывая следующее использование набора доступа этого свойства
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNullбез атрибута компилятор может создать предупреждение, так как для свойства, не допускающего значение NULL, может быть задано значение NULL. Присутствие атрибута подавляет это предупреждение. пример конца
23.5.7.3 Атрибут DisallowNull
Указывает, что значение NULL запрещено в качестве входных данных, даже если соответствующий тип разрешает его.
Пример. Рассмотрим следующее свойство, в котором значение NULL является значением по умолчанию, но клиенты могут задать только значение, отличное от NULL.
#nullable enable public class X { [DisallowNull] public string? ReviewComment { get => _comment; set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null"); } private string? _comment = default; }Метод доступа может возвращать значение по умолчанию
null, поэтому компилятор может предупредить, что перед доступом его необходимо проверить. Кроме того, он предупреждает вызывающих абонентов, что, несмотря на то, что это может быть null, вызывающие операторы не должны явно задать для него значение NULL. пример конца
23.5.7.4 Атрибут DoesNotReturn
Указывает, что заданный метод никогда не возвращается.
Пример. Рассмотрим следующее:
public class X { [DoesNotReturn] private void FailFast() => throw new InvalidOperationException(); public void SetState(object? containedField) { if ((!isInitialized) || (containedField == null)) { FailFast(); } // null check not needed. _field = containedField; } private bool isInitialized = false; private object _field; }Наличие атрибута помогает компилятору разными способами. Во-первых, компилятор может выдавать предупреждение, если есть путь, по которому метод может завершиться без выброса исключения. Во-вторых, компилятор может подавлять предупреждения, допускающие значение NULL, в любом коде после вызова этого метода до тех пор, пока не будет найдено соответствующее предложение catch. В-третьих, неустранимый код не влияет на какие-либо состояния NULL.
Атрибут не изменяет доступность (§13.2) или определенный анализ назначения (§9.4) на основе присутствия этого атрибута. Он используется только для влияния предупреждений о допустимости null. пример конца
23.5.7.5 Атрибут DoesNotReturnIf
Указывает, что заданный метод никогда не возвращается, если связанный bool параметр имеет указанное значение.
Пример. Рассмотрим следующее:
#nullable enable public class X { private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName) { if (!isNull) { throw new ArgumentException(argumentName, $"argument {argumentName} can't be null"); } } public void SetFieldState(object containedField) { ThrowIfNull(containedField == null, nameof(containedField)); // unreachable code when "isInitialized" is false: _field = containedField; } private bool isInitialized = false; private object _field = default!; }пример конца
23.5.7.6 Атрибут MaybeNull
Указывает, что возвращаемое значение, отличное от NULL, может иметь значение NULL.
Пример. Рассмотрим следующий универсальный метод:
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }Идея этого кода заключается в том, что если
Tонstringзаменен, становится заметкой,T?допускающей значение NULL. Однако этот код не является законным, так какTне ограничивается типом ссылок. Однако добавление этого атрибута решает проблему:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }Атрибут сообщает вызывающим пользователям, что контракт подразумевает тип, не допускающий значение NULL, но возвращаемое значение может быть фактически
null. пример конца
23.5.7.7 Атрибут MaybeNullWhen
Указывает, что аргумент, не допускающий значения NULL, может быть, null если метод возвращает указанное bool значение. Это аналогично атрибуту MaybeNull (§23.5.7.6), но включает параметр для указанного возвращаемого значения.
23.5.7.8 Атрибут NotNull
Указывает, что значение, допускающее значение NULL, никогда не будет, null если метод возвращает (а не вызывает).
Пример. Рассмотрим следующее:
#nullable enable public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") => _ = value ?? throw new ArgumentNullException(valueExpression); public static void LogMessage(string? message) { ThrowWhenNull(message, nameof(message)); Console.WriteLine(message.Length); }Если включены ссылочные типы, допускающие значение NULL, метод
ThrowWhenNullкомпилируется без предупреждений. Когда этот метод возвращается,valueаргумент гарантированно неnullбудет. Тем не менее, это приемлемо для вызоваThrowWhenNullс пустой ссылкой. пример конца
23.5.7.9 Атрибут NotNullIfNotNull
Указывает, что возвращаемое значение не null является, если аргумент для указанного параметра не nullявляется.
Пример. Значение NULL возвращаемого значения может зависеть от состояния NULL одного или нескольких аргументов. Чтобы помочь анализу компилятора, можно использовать атрибут
null, если метод всегда возвращает ненулевое значение при условии, что определённые аргументы неNotNullIfNotNull. Рассмотрим следующий метод.#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
urlЕсли аргумент неnullзадан,nullне возвращается. Если включены ссылки, допускающие значение NULL, эта сигнатура работает правильно, если API никогда не принимает аргумент NULL. Однако если аргумент может иметь значение NULL, возвращаемое значение также может быть null. Чтобы правильно выразить этот контракт, запишите этот метод следующим образом:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }пример конца
23.5.7.10 Атрибут NotNullWhen
Указывает, что аргумент, допускающий значение NULL, не будет, null если метод возвращает указанное bool значение.
Пример. Метод
String.IsNullOrEmpty(String)библиотеки возвращаетсяtrue, когда аргумент являетсяnullили пустой строкой. Это форма проверки null: вызывающие не должны проверять аргумент, если метод возвращаетсяfalse. Чтобы сделать метод, подобный этому значению NULL, сделайте тип параметра допустимым ссылочным типом и добавьте атрибут NotNullWhen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }пример конца
23.5.8 Атрибут EnumeratorCancellation
Задает параметр, CancellationToken представляющий асинхронный итератор (§15.15). Аргумент этого параметра должен сочетаться с аргументом, переданным в IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken). Этот объединенный маркер должен быть опрашивлен IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2). Маркеры должны объединяться в один маркер, как будто по CancellationToken.CreateLinkedTokenSource его Token свойству. Объединенный маркер будет отменен, если один из двух исходных маркеров отменен. Объединенный маркер рассматривается как аргумент метода асинхронного итератора (§15.15) в тексте этого метода.
Это ошибка, если System.Runtime.CompilerServices.EnumeratorCancellation атрибут применяется к нескольким параметрам. Компилятор может создать предупреждение, если:
- Атрибут
EnumeratorCancellationприменяется к параметру типа, отличного отCancellationTokenтипа, - или если
EnumeratorCancellationатрибут применяется к параметру метода, который не является асинхронным итератором (§15.15), - или если
EnumeratorCancellationатрибут применяется к параметру метода, который возвращает асинхронный интерфейс перечисления (§15.15.3), а не интерфейс асинхронного перечислителя (§15.15.2).
Итератор не будет иметь доступа к аргументу CancellationToken , GetAsyncEnumerator если атрибуты не имеют этого параметра.
Пример. Метод
GetStringsAsync()является асинхронным итератором. Перед выполнением любой работы для получения следующего значения он проверяет маркер отмены, чтобы определить, следует ли отменить итерацию. Если отмена запрашивается, дальнейшие действия не принимаются.public static async Task ExampleCombination() { var sourceOne = new CancellationTokenSource(); var sourceTwo = new CancellationTokenSource(); await using (IAsyncEnumerator<string> enumerator = GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token)) { while (await enumerator.MoveNextAsync()) { string number = enumerator.Current; if (number == "8") sourceOne.Cancel(); if (number == "5") sourceTwo.Cancel(); Console.WriteLine(number); } } } static async IAsyncEnumerable<string> GetStringsAsync( [EnumeratorCancellation] CancellationToken token) { for (int i = 0; i < 10; i++) { if (token.IsCancellationRequested) yield break; await Task.Delay(1000, token); yield return i.ToString(); } }пример конца
23.6 Атрибуты для взаимодействия
Для взаимодействия с другими языками индексатор может быть реализован с помощью индексированных свойств. Если для индексатора нет IndexerName атрибута, по умолчанию используется имя Item . Атрибут IndexerName позволяет разработчику переопределить это значение по умолчанию и указать другое имя.
Пример. По умолчанию используется
Itemимя индексатора. Это можно переопределить следующим образом:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }Теперь имя индексатора .
TheItemпример конца
ECMA C# draft specification