Поделиться через


22 Атрибуты

22.1 Общие

Большая часть языка C# позволяет программисту указывать декларативную информацию о сущностях, определенных в программе. Например, специальные возможности метода в классе задаются путем декорирования с помощью method_modifier, publicprotectedinternalи .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, bytechardoublefloatintlongsbyteshortstringuintulongushort
  • Тип 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, 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, возникает ошибка во время компиляции.
  • В противном случае — значение .

Если именно один из двух шагов выше приводит к типу, производным от 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:

Пример:

[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)формы. Эти шаги либо приводят к ошибке во время компиляции, либо определяют конструктор CT экземпляра, который можно вызвать во время выполнения.
  • Если C нет общедоступной доступности, возникает ошибка во время компиляции.
  • Для каждого named_argumentArg в N:
    • Давайте будем Name идентификаторомnamed_argumentArg.
    • Nameдолжен определять общедоступное поле или свойство, не статическое чтение и запись.T Если T такого поля или свойства нет, возникает ошибка во время компиляции.
  • Если любой из значений в positional_argument_list или один из значений в P имеет типN, и значение не является хорошо сформированным в соответствии со стандартом Юникод, оно определяется реализацией, равно ли скомпилированному значению времени выполнения (System.String).

    Примечание. В качестве примера строка, содержащая высоко суррогатную единицу кода UTF-16, за которой не следует единица кода с низким уровнем суррогата, не является хорошо сформированной. конечная заметка

  • Сохраните следующие сведения (для создания экземпляра атрибута во время выполнения) в выходных данных сборки компилятором в результате компиляции программы, содержащей атрибут: класс Tатрибута, конструктор C экземпляра в T, PN и связанную сущность Eпрограммы, со значениями, разрешаемыми полностью во время компиляции.

22.4.3 Извлечение экземпляра атрибута во время выполнения

Используя термины, определенные в §22.4.2, экземпляр атрибута, представленный T, CPи, и Nсвязанный с E ним, можно получить во время выполнения из сборкиA, выполнив следующие действия:

  • Выполните действия по обработке во время выполнения для выполнения object_creation_expression формы new T(P)с помощью конструктора C экземпляра и значений, определенных во время компиляции. Эти действия либо приводят к исключению, либо создают экземпляр OT.
  • Для каждого named_argumentArg в 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 (и, следовательно, как экземпляр атрибута получен во время выполнения) выходит за рамки этой спецификации. конечная заметка

Пример. В реализации интерфейса командной строки экземпляры атрибутов в сборке, 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включается вызов, в то время как вызов 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может устранить такие проблемы.

пример конца

Классы условного атрибута 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 . Каждое использование приводит к предупреждению AMain , включающее указанное сообщение: "Этот класс устарел; вместо этого используйте класс 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

пример конца