22 Атрибуты
22.1 Общие
Большая часть языка C# позволяет программисту указывать декларативную информацию о сущностях, определенных в программе. Например, специальные возможности метода в классе задаются путем декорирования с помощью method_modifier, public
protected
internal
и .private
C# позволяет программистам изобретать новые виды декларативной информации, называемые атрибутами. Затем программисты могут присоединять атрибуты к различным сущностям программы и получать сведения о атрибутах в среде выполнения.
Примечание. Например, платформа может определить
HelpAttribute
атрибут, который можно поместить на определенные элементы программы (например, классы и методы), чтобы обеспечить сопоставление этих элементов программы с их документацией. конечная заметка
Атрибуты определяются посредством объявления классов атрибутов (§22.2), которые могут иметь позиционные и именованные параметры (§22.2.3). Атрибуты присоединены к сущностям в программе C# с помощью спецификаций атрибутов (§22.3) и могут быть получены во время выполнения в качестве экземпляров атрибутов (§22.4).
Классы атрибутов 22.2
22.2.1 Общие
Класс, производный от абстрактного класса System.Attribute
, напрямую или косвенно, является классом атрибута. Объявление класса атрибута определяет новый вид атрибута, который можно поместить в сущности программы. По соглашению классы атрибутов называются суффиксом Attribute
. Использование атрибута может включать или пропускать этот суффикс.
Объявление универсального класса не должно использоваться System.Attribute
в качестве прямого или косвенного базового класса.
Пример:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute
пример конца
Использование атрибутов 22.2.2
Атрибут AttributeUsage
(§22.5.2) используется для описания использования класса атрибутов.
AttributeUsage
имеет позиционный параметр (§22.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
имеет именованный параметр (§22.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
имеет другой именованный параметр (§22.2.3), который Inherited
указывает, является ли атрибут, указанный в базовом классе, также наследуется классами, производными от этого базового класса. Если Inherited
для класса атрибута задано значение true, этот атрибут наследуется. Если Inherited
для класса атрибута задано значение false, то этот атрибут не наследуется. Если оно не указано, значение по умолчанию имеет значение true.
Класс X
атрибута, не подключенный AttributeUsage
к нему, как в
class X : Attribute { ... }
эквивалентен следующему:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
22.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 { }
пример конца
Типы параметров атрибута 22.2.4
Типы позиционных и именованных параметров для класса атрибутов ограничены типами параметров атрибута, которые:
- Один из следующих типов:
bool
,byte
char
double
float
int
long
sbyte
short
string
uint
ulong
ushort
- Тип
object
. - Тип
System.Type
. - Типы перечисления.
- Одномерные массивы указанных выше типов.
- Аргумент конструктора или общедоступное поле, которое не имеет одного из этих типов, не должно использоваться в качестве позиционного или именованного параметра в спецификации атрибута.
Спецификация атрибута 22.3
Спецификация атрибута — это приложение ранее определенного атрибута к сущности программы. Атрибут — это часть дополнительных декларативных сведений, указанных для сущности программы. Атрибуты можно указать в глобальной области (чтобы указать атрибуты в содержащей сборке или модуле) и для type_declaration s (§14.7), class_member_declaration s (§15.3), interface_member_declarations (§14.7) 18.4), struct_member_declaration s (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ объявлениеs (§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 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_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
, param
property
, , return
и type
typevar
. Эти имена целевых объектов должны использоваться только в следующих контекстах:
-
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
(§22.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 {}
пример конца
Атрибуты для параметров типа объединяются так же.
22.4 Экземпляры атрибутов
22.4.1 Общие
Экземпляр атрибута — это экземпляр , представляющий атрибут во время выполнения. Атрибут определяется классом атрибута, позициальными аргументами и именованными аргументами. Экземпляр атрибута — это экземпляр класса атрибута, который инициализирован с позициальными и именованными аргументами.
Получение экземпляра атрибута включает как обработку во время компиляции, так и во время выполнения, как описано в следующих подклаузах.
22.4.2 Компиляция атрибута
Компиляция атрибута с классом атрибутов T
, P
, N
и указана в сущности E
программы, компилируется в сборку A
с помощью следующих шагов:
- Выполните действия по обработке во время компиляции для компиляции object_creation_expression новой
T(P)
формы. Эти шаги либо приводят к ошибке во время компиляции, либо определяют конструкторC
T
экземпляра, который можно вызвать во время выполнения. - Если
C
нет общедоступной доступности, возникает ошибка во время компиляции. - Для каждого named_argument
Arg
вN
:- Давайте будем
Name
идентификаторомnamed_argumentArg
. -
Name
должен определять общедоступное поле или свойство, не статическое чтение и запись.T
ЕслиT
такого поля или свойства нет, возникает ошибка во время компиляции.
- Давайте будем
- Если любой из значений в positional_argument_list или один из значений в
P
имеет типN
, и значение не является хорошо сформированным в соответствии со стандартом Юникод, оно определяется реализацией, равно ли скомпилированному значению времени выполнения (System.String
).Примечание. В качестве примера строка, содержащая высоко суррогатную единицу кода UTF-16, за которой не следует единица кода с низким уровнем суррогата, не является хорошо сформированной. конечная заметка
- Сохраните следующие сведения (для создания экземпляра атрибута во время выполнения) в выходных данных сборки компилятором в результате компиляции программы, содержащей атрибут: класс
T
атрибута, конструкторC
экземпляра вT
,P
N
и связанную сущностьE
программы, со значениями, разрешаемыми полностью во время компиляции.
22.4.3 Извлечение экземпляра атрибута во время выполнения
Используя термины, определенные в §22.4.2, экземпляр атрибута, представленный T
, C
P
и, и N
связанный с E
ним, можно получить во время выполнения из сборкиA
, выполнив следующие действия:
- Выполните действия по обработке во время выполнения для выполнения object_creation_expression формы
new T(P)
с помощью конструктораC
экземпляра и значений, определенных во время компиляции. Эти действия либо приводят к исключению, либо создают экземплярO
T
. - Для каждого 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
,P
N
(и связывания его сE
)A
и механизм для указанияE
и извлеченияT
C
P
N
, изA
(и, следовательно, как экземпляр атрибута получен во время выполнения) выходит за рамки этой спецификации. конечная заметка
Пример. В реализации интерфейса командной строки экземпляры атрибутов в сборке,
Help
созданной путем компиляции примера программы в §22.2.3 , можно получить с помощью следующей программы:public sealed class InterrogateHelpUrls { public static void Main(string[] args) { Type helpType = typeof(HelpAttribute); string assemblyName = args[0]; foreach (Type t in Assembly.Load(assemblyName).GetTypes()) { Console.WriteLine($"Type : {t}"); var attributes = t.GetCustomAttributes(helpType, false); var helpers = (HelpAttribute[]) attributes; foreach (var helper in helpers) { Console.WriteLine($"\tUrl : {helper.Url}"); } } } }
пример конца
22.5 Зарезервированные атрибуты
22.5.1 Общие
Ряд атрибутов влияет на язык каким-то образом. Они перечислены ниже:
-
System.AttributeUsageAttribute
(§22.5.2), который используется для описания способов использования класса атрибутов. -
System.Diagnostics.ConditionalAttribute
(§22.5.3) — это многопользовательский класс атрибутов, который используется для определения условных методов и классов условных атрибутов. Этот атрибут указывает условие путем тестирования символа условной компиляции. -
System.ObsoleteAttribute
(§22.5.4), который используется для обозначения элемента как устаревшего. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
(§22.5.5), который используется для создания построителя задач для асинхронного метода. -
System.Runtime.CompilerServices.CallerLineNumberAttribute
(§22.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute
(§22.5.6.3System.Runtime.CompilerServices.CallerMemberNameAttribute
§22.5.6.4), которые используются для предоставления сведений о контексте вызова необязательным параметрам.
Атрибуты статического анализа, допускающие значение NULL (§22.5.7), могут улучшить правильность предупреждений, созданных для значений NULL и состояний NULL (§8.9.5).
Среда выполнения может предоставлять дополнительные атрибуты, определенные реализацией, влияющие на выполнение программы C#.
22.5.2 Атрибут AttributeUsage
AttributeUsage
Атрибут используется для описания способа использования класса атрибута.
Класс, украшенный AttributeUsage
атрибутом, должен быть производным от System.Attribute
прямого или косвенного. В противном случае возникает ошибка во время компиляции.
Примечание. Пример использования этого атрибута см. в разделе §22.2.2. конечная заметка
22.5.3 Условный атрибут
22.5.3.1 Общие
Conditional
Атрибут включает определение условных методов и классов условных атрибутов.
Условные методы 22.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
. - Условный метод не должен быть реализацией метода интерфейса. В противном случае возникает ошибка во время компиляции.
- Параметры условного метода не должны быть выходными параметрами.
Кроме того, ошибка во время компиляции возникает, если делегат создается из условного метода.
Пример: пример
#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
объявляется как условный метод.Class2
Test
Метод вызывает этот метод. Так как символ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
включается вызов, в то время как вызовF
Class2
F
Class3
включено.пример конца
Использование условных методов в цепочке наследования может быть запутанным. Вызовы условного метода с помощью 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может устранить такие проблемы.пример конца
Классы условного атрибута 22.5.3.3
Класс атрибута (§22.2), украшенный одним или несколькими Conditional
атрибутами, является классом условного атрибута. Таким образом, класс условного атрибута связан с символами условной компиляции, объявленными в его Conditional
атрибутах.
Пример:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttribute
объявляется как класс условного атрибута, связанный с символами условной компиляцииALPHA
иBETA
.пример конца
Спецификации атрибутов (§22.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
, спецификация атрибутаClass1
Test включена, а спецификацияTest
атрибутаClass2
опущена.пример конца
22.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
. Каждое использование приводит к предупреждениюA
Main
, включающее указанное сообщение: "Этот класс устарел; вместо этого используйте классB
".пример конца
22.5.5 Атрибут AsyncMethodBuilder
Этот атрибут описан в разделе §15.15.1.
Атрибуты caller-info 22.5.6
22.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
будет игнорироваться. Лексическое упорядочение этих атрибутов не имеет значения.
22.5.6.2 Атрибут CallerLineNumber
Атрибут System.Runtime.CompilerServices.CallerLineNumberAttribute
допускается для необязательных параметров, если существует стандартное неявное преобразование (§10.4.2) из константного значения int.MaxValue
в тип параметра. Это гарантирует, что любое не отрицательное число строки до этого значения может быть передано без ошибок.
Если вызов функции из расположения в исходном коде пропускает необязательный параметр с CallerLineNumberAttribute
помощью, то числовый литерал, представляющий номер строки расположения, используется в качестве аргумента для вызова вместо значения параметра по умолчанию.
Если вызов охватывает несколько строк, выбранная строка зависит от реализации.
Номер строки может повлиять на #line
директивы (§6.5.8).
22.5.6.3 Атрибут CallerFilePath
Атрибут System.Runtime.CompilerServices.CallerFilePathAttribute
допускается для необязательных параметров, если существует стандартное неявное преобразование (§10.4.2) в string
тип параметра.
Если вызов функции из расположения в исходном коде пропускает необязательный параметр с CallerFilePathAttribute
помощью, строковый литерал, представляющий путь к файлу расположения, используется в качестве аргумента для вызова вместо значения параметра по умолчанию.
Формат пути к файлу зависит от реализации.
Путь к файлу может повлиять на #line
директивы (§6.5.8).
22.5.6.4 Атрибут CallerMemberName
Атрибут System.Runtime.CompilerServices.CallerMemberNameAttribute
допускается для необязательных параметров, если существует стандартное неявное преобразование (§10.4.2) в string
тип параметра.
Если вызов функции из расположения элемента функции или атрибута, примененного к самому элементу функции, или к его типу возвращаемого типа, параметрам или параметрам типа в исходном коде не следует указывать необязательный параметр, CallerMemberNameAttribute
строковый литерал, представляющий имя этого элемента, используется в качестве аргумента для вызова вместо значения параметра по умолчанию.
Для вызовов, происходящих в универсальных методах, используется только имя метода без списка параметров типа.
Для вызовов, происходящих в явных реализациях элементов интерфейса, используется только имя метода без предыдущей квалификации интерфейса.
Для вызовов, происходящих в свойствах или методах доступа к событиям, имя члена используется для самого свойства или события.
Для вызовов, происходящих в методах доступа индексатора, используется имя члена, предоставленное элементом IndexerNameAttribute
(§22.6) в члене индексатора, если он присутствует, или имя Item
по умолчанию в противном случае.
Для вызовов, происходящих в полях или инициализаторах событий, имя члена, используемое, — это имя поля или события, инициализированного события.
Для вызовов, происходящих в объявлениях конструкторов экземпляров, статических конструкторов, завершающих и операторов, используемых именем члена, зависит от реализации.
Атрибуты анализа кода 22.5.7
22.5.7.1 Общие
Атрибуты в этом разделе используются для предоставления дополнительных сведений для поддержки компилятора, обеспечивающего допустимость null и состояние NULL, диагностика (§8.9.5). Компилятор не требуется для выполнения каких-либо диагностика состояния NULL. Наличие или отсутствие этих атрибутов не влияет на язык или поведение программы. Компилятор, который не предоставляет диагностика состояния NULL, должен считывать и игнорировать наличие этих атрибутов. Компилятор, предоставляющий диагностика состояния NULL, должен использовать значение, определенное в этом разделе, для любого из этих атрибутов, используемых для информирования о диагностика.
Атрибуты анализа кода объявляются в пространстве System.Diagnostics.CodeAnalysis
имен.
Attribute | Значение |
---|---|
AllowNull (§22.5.7.2) |
Аргумент, не допускающий значение NULL, может принимать значение NULL. |
DisallowNull (§22.5.7.3) |
Аргумент, допускающий значение NULL, никогда не должен принимать значение NULL. |
MaybeNull (§22.5.7.6) |
Возвращаемое значение, не допускающее значение NULL, может быть равно NULL. |
NotNull (§22.5.7.8) |
Возвращаемое значение, допускающее значение NULL, никогда не будет равно NULL. |
MaybeNullWhen (§22.5.7.7) |
Аргумент, не допускающий значение NULL, может иметь значение NULL, если метод возвращает указанное значение bool . |
NotNullWhen (§22.5.7.10) |
Аргумент, допускающий значение NULL, не будет иметь значение NULL, если метод возвращает указанное bool значение. |
NotNullIfNotNull (§22.5.7.9) |
Возвращаемое значение не равно NULL, если аргумент для указанного параметра не имеет значения NULL. |
DoesNotReturn (§22.5.7.4) |
Этот метод никогда не возвращает значение . |
DoesNotReturnIf (§22.5.7.5) |
Этот метод никогда не возвращает значение, если связанный параметр bool имеет указанное значение. |
Следующие разделы в §22.5.7.1 являются условно нормативными.
22.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. Присутствие атрибута подавляет это предупреждение. пример конца
22.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. пример конца
22.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. пример конца
22.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!; }
пример конца
22.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
. пример конца
22.5.7.7 Атрибут MaybeNullWhen
Указывает, что аргумент, не допускающий значения NULL, может быть, null
если метод возвращает указанное bool
значение. Это аналогично атрибуту MaybeNull
(§22.5.7.6), но включает параметр для указанного возвращаемого значения.
22.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
с пустой ссылкой. пример конца
22.5.7.9 Атрибут NotNullIfNotNull
Указывает, что возвращаемое значение не null
является, если аргумент для указанного параметра не null
является.
Пример. Значение NULL возвращаемого значения может зависеть от состояния NULL одного или нескольких аргументов. Чтобы помочь анализу компилятора, можно использовать атрибут
NotNullIfNotNull
, если метод всегда возвращает ненулевое значение при условии, что определённые аргументы неnull
. Рассмотрим следующий метод.#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
url
Если аргумент неnull
задан,null
не возвращается. Если включены ссылки, допускающие значение NULL, эта сигнатура работает правильно, если API никогда не принимает аргумент NULL. Однако если аргумент может иметь значение NULL, возвращаемое значение также может быть null. Чтобы правильно выразить этот контракт, запишите этот метод следующим образом:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }
пример конца
22.5.7.10 Атрибут NotNullWhen
Указывает, что аргумент, допускающий значение NULL, не будет, null
если метод возвращает указанное bool
значение.
Пример. Метод
String.IsNullOrEmpty(String)
библиотеки возвращаетсяtrue
, когда аргумент являетсяnull
или пустой строкой. Это форма проверки null: вызывающие не должны проверять аргумент, если метод возвращаетсяfalse
. Чтобы сделать метод, подобный этому значению NULL, сделайте тип параметра допустимым ссылочным типом и добавьте атрибут NotNullWhen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
пример конца
22.6 Атрибуты для взаимодействия
Для взаимодействия с другими языками индексатор может быть реализован с помощью индексированных свойств. Если для индексатора нет IndexerName
атрибута, по умолчанию используется имя Item
. Атрибут IndexerName
позволяет разработчику переопределить это значение по умолчанию и указать другое имя.
Пример. По умолчанию используется
Item
имя индексатора. Это можно переопределить следующим образом:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
Теперь имя индексатора .
TheItem
пример конца
ECMA C# draft specification