Skip to content

Latest commit

 

History

History
1112 lines (923 loc) · 56.9 KB

attributes.md

File metadata and controls

1112 lines (923 loc) · 56.9 KB

22 Attributes

22.1 General

Much of the C# language enables the programmer to specify declarative information about the entities defined in the program. For example, the accessibility of a method in a class is specified by decorating it with the method_modifiers public, protected, internal, and private.

C# enables programmers to invent new kinds of declarative information, called attributes. Programmers can then attach attributes to various program entities, and retrieve attribute information in a run-time environment.

Note: For instance, a framework might define a HelpAttribute attribute that can be placed on certain program elements (such as classes and methods) to provide a mapping from those program elements to their documentation. end note

Attributes are defined through the declaration of attribute classes (§22.2), which can have positional and named parameters (§22.2.3). Attributes are attached to entities in a C# program using attribute specifications (§22.3), and can be retrieved at run-time as attribute instances (§22.4).

22.2 Attribute classes

22.2.1 General

A class that derives from the abstract class System.Attribute, whether directly or indirectly, is an attribute class. The declaration of an attribute class defines a new kind of attribute that can be placed on program entities. By convention, attribute classes are named with a suffix of Attribute. Uses of an attribute may either include or omit this suffix.

A generic class declaration shall not use System.Attribute as a direct or indirect base class.

Example:

public class B : Attribute {}
public class C<T> : B {} // Error – generic cannot be an attribute

end example

22.2.2 Attribute usage

The attribute AttributeUsage (§22.5.2) is used to describe how an attribute class can be used.

AttributeUsage has a positional parameter (§22.2.3) that enables an attribute class to specify the kinds of program entities on which it can be used.

Example: The following example defines an attribute class named SimpleAttribute that can be placed on class_declarations and interface_declarations only, and shows several uses of the Simple attribute.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute : Attribute
{ 
    ... 
}

[Simple] class Class1 {...}
[Simple] interface Interface1 {...}

Although this attribute is defined with the name SimpleAttribute, when this attribute is used, the Attribute suffix may be omitted, resulting in the short name Simple. Thus, the example above is semantically equivalent to the following

[SimpleAttribute] class Class1 {...}
[SimpleAttribute] interface Interface1 {...}

end example

AttributeUsage has a named parameter (§22.2.3), called AllowMultiple, which indicates whether the attribute can be specified more than once for a given entity. If AllowMultiple for an attribute class is true, then that attribute class is a multi-use attribute class, and can be specified more than once on an entity. If AllowMultiple for an attribute class is false or it is unspecified, then that attribute class is a single-use attribute class, and can be specified at most once on an entity.

Example: The following example defines a multi-use attribute class named AuthorAttribute and shows a class declaration with two uses of the Author attribute:

[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 
{
    ...
}

end example

AttributeUsage has another named parameter (§22.2.3), called Inherited, which indicates whether the attribute, when specified on a base class, is also inherited by classes that derive from that base class. If Inherited for an attribute class is true, then that attribute is inherited. If Inherited for an attribute class is false then that attribute is not inherited. If it is unspecified, its default value is true.

An attribute class X not having an AttributeUsage attribute attached to it, as in

class X : Attribute { ... }

is equivalent to the following:

[AttributeUsage(
   AttributeTargets.All,
   AllowMultiple = false,
   Inherited = true)
]
class X : Attribute { ... }

22.2.3 Positional and named parameters

Attribute classes can have positional parameters and named parameters. Each public instance constructor for an attribute class defines a valid sequence of positional parameters for that attribute class. Each non-static public read-write field and property for an attribute class defines a named parameter for the attribute class. For a property to define a named parameter, that property shall have both a public get accessor and a public set accessor.

Example: The following example defines an attribute class named HelpAttribute that has one positional parameter, url, and one named parameter, Topic. Although it is non-static and public, the property Url does not define a named parameter, since it is not read-write. Two uses of this attribute are also shown:

[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
{
}

end example

22.2.4 Attribute parameter types

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • The type object.
  • The type System.Type.
  • Enum types.
  • Single-dimensional arrays of the above types.
  • A constructor argument or public field that does not have one of these types, shall not be used as a positional or named parameter in an attribute specification.

22.3 Attribute specification

Attribute specification is the application of a previously defined attribute to a program entity. An attribute is a piece of additional declarative information that is specified for a program entity. Attributes can be specified at global scope (to specify attributes on the containing assembly or module) and for type_declarations (§14.7), class_member_declarations (§15.3), interface_member_declarations (§18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_declarations (§15.8), elements of parameter_lists (§15.6.2), and elements of type_parameter_lists (§15.2.3).

Attributes are specified in attribute sections. An attribute section consists of a pair of square brackets, which surround a comma-separated list of one or more attributes. The order in which attributes are specified in such a list, and the order in which sections attached to the same program entity are arranged, is not significant. For instance, the attribute specifications [A][B], [B][A], [A, B], and [B, A] are equivalent.

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
    ;

For the production global_attribute_target, and in the text below, identifier shall have a spelling equal to assembly or module, where equality is that defined in §6.4.3. For the production attribute_target, and in the text below, identifier shall have a spelling that is not equal to assembly or module, using the same definition of equality as above.

An attribute consists of an attribute_name and an optional list of positional and named arguments. The positional arguments (if any) precede the named arguments. A positional argument consists of an attribute_argument_expression; a named argument consists of a name, followed by an equal sign, followed by an attribute_argument_expression, which, together, are constrained by the same rules as simple assignment. The order of named arguments is not significant.

Note: For convenience, a trailing comma is allowed in a global_attribute_section and an attribute_section, just as one is allowed in an array_initializer (§17.7). end note

The attribute_name identifies an attribute class.

When an attribute is placed at the global level, a global_attribute_target_specifier is required. When the global_attribute_target is equal to:

  • assembly — the target is the containing assembly
  • module — the target is the containing module

No other values for global_attribute_target are allowed.

The standardized attribute_target names are event, field, method, param, property, return, type, and typevar. These target names shall only be used in the following contexts:

  • event — an event.
  • field — a field. A field-like event (i.e., one without accessors) (§15.8.2) and an automatically implemented property (§15.7.4) can also have an attribute with this target.
  • method — a constructor, finalizer, method, operator, property get and set accessors, indexer get and set accessors, and event add and remove accessors. A field-like event (i.e., one without accessors) can also have an attribute with this target.
  • param — a property set accessor, an indexer set accessor, event add and remove accessors, and a parameter in a constructor, method, and operator.
  • property — a property and an indexer.
  • return — a delegate, method, operator, property get accessor, and indexer get accessor.
  • type — a delegate, class, struct, enum, and interface.
  • typevar — a type parameter.

Certain contexts permit the specification of an attribute on more than one target. A program can explicitly specify the target by including an attribute_target_specifier. Without an attribute_target_specifier a default is applied, but an attribute_target_specifier can be used to affirm or override the default. The contexts are resolved as follows:

  • For an attribute on a delegate declaration the default target is the delegate. Otherwise when the attribute_target is equal to:
    • type — the target is the delegate
    • return — the target is the return value
  • For an attribute on a method declaration the default target is the method. Otherwise when the attribute_target is equal to:
    • method — the target is the method
    • return — the target is the return value
  • For an attribute on an operator declaration the default target is the operator. Otherwise when the attribute_target is equal to:
    • method — the target is the operator
    • return — the target is the return value
  • For an attribute on a get accessor declaration for a property or indexer declaration the default target is the associated method. Otherwise when the attribute_target is equal to:
    • method — the target is the associated method
    • return — the target is the return value
  • For an attribute specified on a set accessor for a property or indexer declaration the default target is the associated method. Otherwise when the attribute_target is equal to:
    • method — the target is the associated method
    • param — the target is the lone implicit parameter
  • For an attribute on an automatically implemented property declaration the default target is the property. Otherwise when the attribute_target is equal to:
    • field — the target is the compiler-generated backing field for the property
  • For an attribute specified on an event declaration that omits event_accessor_declarations the default target is the event declaration. Otherwise when the attribute_target is equal to:
    • event — the target is the event declaration
    • field — the target is the field
    • method — the targets are the methods
  • In the case of an event declaration that does not omit event_accessor_declarations the default target is the method.
    • method — the target is the associated method
    • param — the target is the lone parameter

In all other contexts, inclusion of an attribute_target_specifier is permitted but unnecessary.

Example: a class declaration may either include or omit the specifier type:

[type: Author("Brian Kernighan")]
class Class1 {}

[Author("Dennis Ritchie")]
class Class2 {}

end example.

An implementation can accept other attribute_targets, the purposes of which are implementation defined. An implementation that does not recognize such an attribute_target shall issue a warning and ignore the containing attribute_section.

By convention, attribute classes are named with a suffix of Attribute. An attribute_name can either include or omit this suffix. Specifically, an attribute_name is resolved as follows:

  • If the right-most identifier of the attribute_name is a verbatim identifier (§6.4.3), then the attribute_name is resolved as a type_name (§7.8). If the result is not a type derived from System.Attribute, a compile-time error occurs.
  • Otherwise,
    • The attribute_name is resolved as a type_name (§7.8) except any errors are suppressed. If this resolution is successful and results in a type derived from System.Attribute then the type is the result of this step.
    • The characters Attribute are appended to the right-most identifier in the attribute_name and the resulting string of tokens is resolved as a type_name (§7.8) except any errors are suppressed. If this resolution is successful and results in a type derived from System.Attribute then the type is the result of this step.

If exactly one of the two steps above results in a type derived from System.Attribute, then that type is the result of the attribute_name. Otherwise a compile-time error occurs.

Example: If an attribute class is found both with and without this suffix, an ambiguity is present, and a compile-time error results. If the attribute_name is spelled such that its right-most identifier is a verbatim identifier (§6.4.3), then only an attribute without a suffix is matched, thus enabling such an ambiguity to be resolved. The example

[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 {}

shows two attribute classes named Example and ExampleAttribute. The attribute [Example] is ambiguous, since it could refer to either Example or ExampleAttribute. Using a verbatim identifier allows the exact intent to be specified in such rare cases. The attribute [ExampleAttribute] is not ambiguous (although it would be if there was an attribute class named ExampleAttributeAttribute!). If the declaration for class Example is removed, then both attributes refer to the attribute class named ExampleAttribute, as follows:

[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 {}

end example

It is a compile-time error to use a single-use attribute class more than once on the same entity.

Example: The example

[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 {}

results in a compile-time error because it attempts to use HelpString, which is a single-use attribute class, more than once on the declaration of Class1.

end example

An expression E is an attribute_argument_expression if all of the following statements are true:

  • The type of E is an attribute parameter type (§22.2.4).
  • At compile-time, the value of E can be resolved to one of the following:
    • A constant value.
    • A System.Type object obtained using a typeof_expression (§12.8.18) specifying a non-generic type, a closed constructed type (§8.4.3), or an unbound generic type (§8.4.4), but not an open type (§8.4.3).
    • A single-dimensional array of attribute_argument_expressions.

Example:

[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;
}

end example

The attributes of a type declared in multiple parts are determined by combining, in an unspecified order, the attributes of each of its parts. If the same attribute is placed on multiple parts, it is equivalent to specifying that attribute multiple times on the type.

Example: The two parts:

[Attr1, Attr2("hello")]
partial class A {}

[Attr3, Attr2("goodbye")]
partial class A {}

are equivalent to the following single declaration:

[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}

end example

Attributes on type parameters combine in the same way.

22.4 Attribute instances

22.4.1 General

An attribute instance is an instance that represents an attribute at run-time. An attribute is defined with an attribute class, positional arguments, and named arguments. An attribute instance is an instance of the attribute class that is initialized with the positional and named arguments.

Retrieval of an attribute instance involves both compile-time and run-time processing, as described in the following subclauses.

22.4.2 Compilation of an attribute

The compilation of an attribute with attribute class T, positional_argument_list P, named_argument_list N, and specified on a program entity E is compiled into an assembly A via the following steps:

  • Follow the compile-time processing steps for compiling an object_creation_expression of the form new T(P). These steps either result in a compile-time error, or determine an instance constructor C on T that can be invoked at run-time.
  • If C does not have public accessibility, then a compile-time error occurs.
  • For each named_argument Arg in N:
    • Let Name be the identifier of the named_argument Arg.
    • Name shall identify a non-static read-write public field or property on T. If T has no such field or property, then a compile-time error occurs.
  • If any of the values within positional_argument_list P or one of the values within named_argument_list N is of type System.String and the value is not well-formed as defined by the Unicode Standard, it is implementation-defined whether the value compiled is equal to the run-time value retrieved (§22.4.3).

    Note: As an example, a string which contains a high surrogate UTF-16 code unit which isn’t immediately followed by a low surrogate code unit is not well-formed. end note

  • Store the following information (for run-time instantiation of the attribute) in the assembly output by the compiler as a result of compiling the program containing the attribute: the attribute class T, the instance constructor C on T, the positional_argument_list P, the named_argument_list N, and the associated program entity E, with the values resolved completely at compile-time.

22.4.3 Run-time retrieval of an attribute instance

Using the terms defined in §22.4.2, the attribute instance represented by T, C, P, and N, and associated with E can be retrieved at run-time from the assembly A using the following steps:

  • Follow the run-time processing steps for executing an object_creation_expression of the form new T(P), using the instance constructor C and values as determined at compile-time. These steps either result in an exception, or produce an instance O of T.
  • For each named_argument Arg in N, in order:
    • Let Name be the identifier of the named_argument Arg. If Name does not identify a non-static public read-write field or property on O, then an exception is thrown.
    • Let Value be the result of evaluating the attribute_argument_expression of Arg.
    • If Name identifies a field on O, then set this field to Value.
    • Otherwise, Name identifies a property on O. Set this property to Value.
    • The result is O, an instance of the attribute class T that has been initialized with the positional_argument_list P and the named_argument_list N.

Note: The format for storing T, C, P, N (and associating it with E) in A and the mechanism to specify E and retrieve T, C, P, N from A (and hence how an attribute instance is obtained at runtime) is beyond the scope of this specification. end note

Example: In an implementation of the CLI, the Help attribute instances in the assembly created by compiling the example program in §22.2.3 can be retrieved with the following program:

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}");
            }
        }
    }
}

end example

22.5 Reserved attributes

22.5.1 General

A number of attributes affect the language in some way. These attributes include:

  • System.AttributeUsageAttribute (§22.5.2), which is used to describe the ways in which an attribute class can be used.
  • System.Diagnostics.ConditionalAttribute (§22.5.3), is a multi-use attribute class which is used to define conditional methods and conditional attribute classes. This attribute indicates a condition by testing a conditional compilation symbol.
  • System.ObsoleteAttribute (§22.5.4), which is used to mark a member as obsolete.
  • System.Runtime.CompilerServices.AsyncMethodBuilderAttribute (§22.5.5), which is used to establish a task builder for an async method.
  • System.Runtime.CompilerServices.CallerLineNumberAttribute (§22.5.6.2), System.Runtime.CompilerServices.CallerFilePathAttribute (§22.5.6.3), and System.Runtime.CompilerServices.CallerMemberNameAttribute (§22.5.6.4), which are used to supply information about the calling context to optional parameters.

The Nullable static analysis attributes (§22.5.7) can improve the correctness of warnings generated for nullabilities and null states (§8.9.5).

An execution environment may provide additional implementation-defined attributes that affect the execution of a C# program.

22.5.2 The AttributeUsage attribute

The attribute AttributeUsage is used to describe the manner in which the attribute class can be used.

A class that is decorated with the AttributeUsage attribute shall derive from System.Attribute, either directly or indirectly. Otherwise, a compile-time error occurs.

Note: For an example of using this attribute, see §22.2.2. end note

22.5.3 The Conditional attribute

22.5.3.1 General

The attribute Conditional enables the definition of conditional methods and conditional attribute classes.

22.5.3.2 Conditional methods

A method decorated with the Conditional attribute is a conditional method. Each conditional method is thus associated with the conditional compilation symbols declared in its Conditional attributes.

Example:

class Eg
{
    [Conditional("ALPHA")]
    [Conditional("BETA")]
    public static void M()
    {
        // ...
    }
}

declares Eg.M as a conditional method associated with the two conditional compilation symbols ALPHA and BETA.

end example

A call to a conditional method is included if one or more of its associated conditional compilation symbols is defined at the point of call, otherwise the call is omitted.

A conditional method is subject to the following restrictions:

  • The conditional method shall be a method in a class_declaration or struct_declaration. A compile-time error occurs if the Conditional attribute is specified on a method in an interface declaration.
  • The conditional method shall have a return type of void.
  • The conditional method shall not be marked with the override modifier. A conditional method can be marked with the virtual modifier, however. Overrides of such a method are implicitly conditional, and shall not be explicitly marked with a Conditional attribute.
  • The conditional method shall not be an implementation of an interface method. Otherwise, a compile-time error occurs.
  • The parameters of the conditional method shall not be output parameters.

In addition, a compile-time error occurs if a delegate is created from a conditional method.

Example: The example

#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();
    }
}

declares Class1.M as a conditional method. Class2’s Test method calls this method. Since the conditional compilation symbol DEBUG is defined, if Class2.Test is called, it will call M. If the symbol DEBUG had not been defined, then Class2.Test would not call Class1.M.

end example

It is important to understand that the inclusion or exclusion of a call to a conditional method is controlled by the conditional compilation symbols at the point of the call.

Example: In the following code

// 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
    }
}

the classes Class2 and Class3 each contain calls to the conditional method Class1.F, which is conditional based on whether or not DEBUG is defined. Since this symbol is defined in the context of Class2 but not Class3, the call to F in Class2 is included, while the call to F in Class3 is omitted.

end example

The use of conditional methods in an inheritance chain can be confusing. Calls made to a conditional method through base, of the form base.M, are subject to the normal conditional method call rules.

Example: In the following code

// 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 includes a call to the M defined in its base class. This call is omitted because the base method is conditional based on the presence of the symbol DEBUG, which is undefined. Thus, the method writes to the console “Class2.M executed” only. Judicious use of pp_declarations can eliminate such problems.

end example

22.5.3.3 Conditional attribute classes

An attribute class (§22.2) decorated with one or more Conditional attributes is a conditional attribute class. A conditional attribute class is thus associated with the conditional compilation symbols declared in its Conditional attributes.

Example:

[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}

declares TestAttribute as a conditional attribute class associated with the conditional compilations symbols ALPHA and BETA.

end example

Attribute specifications (§22.3) of a conditional attribute are included if one or more of its associated conditional compilation symbols is defined at the point of specification, otherwise the attribute specification is omitted.

It is important to note that the inclusion or exclusion of an attribute specification of a conditional attribute class is controlled by the conditional compilation symbols at the point of the specification.

Example: In the example

// 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 {}

the classes Class1 and Class2 are each decorated with attribute Test, which is conditional based on whether or not DEBUG is defined. Since this symbol is defined in the context of Class1 but not Class2, the specification of the Test attribute on Class1 is included, while the specification of the Test attribute on Class2 is omitted.

end example

22.5.4 The Obsolete attribute

The attribute Obsolete is used to mark types and members of types that should no longer be used.

If a program uses a type or member that is decorated with the Obsolete attribute, the compiler shall issue a warning or an error. Specifically, the compiler shall issue a warning if no error parameter is provided, or if the error parameter is provided and has the value false. The compiler shall issue an error if the error parameter is specified and has the value true.

Example: In the following code

[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();
    }
}

the class A is decorated with the Obsolete attribute. Each use of A in Main results in a warning that includes the specified message, “This class is obsolete; use class B instead”.

end example

22.5.5 The AsyncMethodBuilder attribute

This attribute is described in §15.15.1.

22.5.6 Caller-info attributes

22.5.6.1 General

For purposes such as logging and reporting, it is sometimes useful for a function member to obtain certain compile-time information about the calling code. The caller-info attributes provide a way to pass such information transparently.

When an optional parameter is annotated with one of the caller-info attributes, omitting the corresponding argument in a call does not necessarily cause the default parameter value to be substituted. Instead, if the specified information about the calling context is available, that information will be passed as the argument value.

Example:

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);
}

A call to Log() with no arguments would print the line number and file path of the call, as well as the name of the member within which the call occurred.

end example

Caller-info attributes can occur on optional parameters anywhere, including in delegate declarations. However, the specific caller-info attributes have restrictions on the types of the parameters they can attribute, so that there will always be an implicit conversion from a substituted value to the parameter type.

It is an error to have the same caller-info attribute on a parameter of both the defining and implementing part of a partial method declaration. Only caller-info attributes in the defining part are applied, whereas caller-info attributes occurring only in the implementing part are ignored.

Caller information does not affect overload resolution. As the attributed optional parameters are still omitted from the source code of the caller, overload resolution ignores those parameters in the same way it ignores other omitted optional parameters (§12.6.4).

Caller information is only substituted when a function is explicitly invoked in source code. Implicit invocations such as implicit parent constructor calls do not have a source location and will not substitute caller information. Also, calls that are dynamically bound will not substitute caller information. When a caller-info attributed parameter is omitted in such cases, the specified default value of the parameter is used instead.

One exception is query expressions. These are considered syntactic expansions, and if the calls they expand to omit optional parameters with caller-info attributes, caller information will be substituted. The location used is the location of the query clause which the call was generated from.

If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: CallerLineNumber, CallerFilePath, CallerMemberName. Consider the following parameter declaration:

[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...

CallerLineNumber takes precedence, and the other two attributes are ignored. If CallerLineNumber were omitted, CallerFilePath would take precedence, and CallerMemberName would be ignored. The lexical ordering of these attributes is irrelevant.

22.5.6.2 The CallerLineNumber attribute

The attribute System.Runtime.CompilerServices.CallerLineNumberAttribute is allowed on optional parameters when there is a standard implicit conversion (§10.4.2) from the constant value int.MaxValue to the parameter’s type. This ensures that any non-negative line number up to that value can be passed without error.

If a function invocation from a location in source code omits an optional parameter with the CallerLineNumberAttribute, then a numeric literal representing that location’s line number is used as an argument to the invocation instead of the default parameter value.

If the invocation spans multiple lines, the line chosen is implementation-dependent.

The line number may be affected by #line directives (§6.5.8).

22.5.6.3 The CallerFilePath attribute

The attribute System.Runtime.CompilerServices.CallerFilePathAttribute is allowed on optional parameters when there is a standard implicit conversion (§10.4.2) from string to the parameter’s type.

If a function invocation from a location in source code omits an optional parameter with the CallerFilePathAttribute, then a string literal representing that location’s file path is used as an argument to the invocation instead of the default parameter value.

The format of the file path is implementation-dependent.

The file path may be affected by #line directives (§6.5.8).

22.5.6.4 The CallerMemberName attribute

The attribute System.Runtime.CompilerServices.CallerMemberNameAttribute is allowed on optional parameters when there is a standard implicit conversion (§10.4.2) from string to the parameter’s type.

If a function invocation from a location within the body of a function member or within an attribute applied to the function member itself or its return type, parameters or type parameters in source code omits an optional parameter with the CallerMemberNameAttribute, then a string literal representing the name of that member is used as an argument to the invocation instead of the default parameter value.

For invocations that occur within generic methods, only the method name itself is used, without the type parameter list.

For invocations that occur within explicit interface member implementations, only the method name itself is used, without the preceding interface qualification.

For invocations that occur within property or event accessors, the member name used is that of the property or event itself.

For invocations that occur within indexer accessors, the member name used is that supplied by an IndexerNameAttribute (§22.6) on the indexer member, if present, or the default name Item otherwise.

For invocations that occur within field or event initializers, the member name used is the name of the field or event being initialized.

For invocations that occur within declarations of instance constructors, static constructors, finalizers and operators the member name used is implementation-dependent.

22.5.7 Code analysis attributes

22.5.7.1 General

The attributes in this section are used to provide additional information to support a compiler that provides nullability and null-state diagnostics (§8.9.5). A compiler isn’t required to perform any null-state diagnostics. The presence or absence of these attributes do not affect the language nor the behavior of a program. A compiler that doesn’t provide null-state diagnostics shall read and ignore the presence of these attributes. A compiler that provides null-state diagnostics shall use the meaning defined in this section for any of these attributes which it uses to inform its diagnostics.

The code-analysis attributes are declared in namespace System.Diagnostics.CodeAnalysis.

Attribute Meaning
AllowNull (§22.5.7.2) A non-nullable argument may be null.
DisallowNull (§22.5.7.3) A nullable argument should never be null.
MaybeNull (§22.5.7.6) A non-nullable return value may be null.
NotNull (§22.5.7.8) A nullable return value will never be null.
MaybeNullWhen (§22.5.7.7) A non-nullable argument may be null when the method returns the specified bool value.
NotNullWhen (§22.5.7.10) A nullable argument won’t be null when the method returns the specified bool value.
NotNullIfNotNull (§22.5.7.9) A return value isn’t null if the argument for the specified parameter isn’t null.
DoesNotReturn (§22.5.7.4) This method never returns.
DoesNotReturnIf (§22.5.7.5) This method never returns if the associated bool parameter has the specified value.

The following sections in §22.5.7.1 are conditionally normative.

22.5.7.2 The AllowNull attribute

Specifies that a null value is allowed as an input even if the corresponding type disallows it.

Example: Consider the following read/write property that never returns null because it has a reasonable default value. However, a user can give null to the set accessor to set the property to that default value.

#nullable enable
public class X
{
    [AllowNull]
    public string ScreenName
    {
        get => _screenName;
        set => _screenName = value ?? GenerateRandomScreenName();
    }
    private string _screenName = GenerateRandomScreenName();
    private static string GenerateRandomScreenName() => ...;
}

Given the following use of that property’s set accessor

var v = new X();
v.ScreenName = null;   // may warn without attribute AllowNull

without the attribute, the compiler may generate a warning because the non-nullable-typed property appears to be set to a null value. The presence of the attribute suppresses that warning. end example

22.5.7.3 The DisallowNull attribute

Specifies that a null value is disallowed as an input even if the corresponding type allows it.

Example: Consider the following property in which null is the default value, but clients can only set it to a non-null value.

#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;
}

The get accessor could return the default value of null, so the compiler may warn that it must be checked before access. Furthermore, it warns callers that, even though it could be null, callers shouldn’t explicitly set it to null. end example

22.5.7.4 The DoesNotReturn attribute

Specifies that a given method never returns.

Example: Consider the following:

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;
}

The presence of the attribute helps the compiler in a number of ways. First, the compiler can issue a warning if there’s a path where the method can exit without throwing an exception. Second, the compiler can suppress nullable warnings in any code after a call to that method, until an appropriate catch clause is found. Third, the unreachable code won’t affect any null states.

The attribute does not change reachability (§13.2) or definite assignment (§9.4) analysis based on the presence of this attribute. It is used only to impact nullability warnings. end example

22.5.7.5 The DoesNotReturnIf attribute

Specifies that a given method never returns if the associated bool parameter has the specified value.

Example: Consider the following:

#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!;
}

end example

22.5.7.6 The MaybeNull attribute

Specifies that a non-nullable return value may be null.

Example: Consider the following generic method:

#nullable enable
public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

The idea of this code is that if T is replaced by string, T? becomes a nullable annotation. However, this code is not legal because T is not constrained to be a reference type. However, adding this attribute solves the problem:

#nullable enable
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

The attribute informs callers that the contract implies a non-nullable type, but the return value may actually be null. end example

22.5.7.7 The MaybeNullWhen attribute

Specifies that a non-nullable argument may be null when the method returns the specified bool value. This is similar to the MaybeNull attribute (§22.5.7.6), but includes a parameter for the specified return value.

22.5.7.8 The NotNull attribute

Specifies that a nullable value will never be null if the method returns (rather than throwing).

Example: Consider the following:

#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);
}

When null reference types are enabled, method ThrowWhenNull compiles without warnings. When that method returns, the value argument is guaranteed to be not null. However, it’s acceptable to call ThrowWhenNull with a null reference. end example

22.5.7.9 The NotNullIfNotNull attribute

Specifies that a return value isn’t null if the argument for the specified parameter isn’t null.

Example: The null state of a return value could depend on the null state of one or more arguments. To assist the compiler’s analysis when a method always returns a non-null value when certain arguments are not null the NotNullIfNotNull attribute may be used. Consider the following method:

#nullable enable
string GetTopLevelDomainFromFullUrl(string url) { ... }

If the url argument isn’t null, null isn’t returned. When nullable references are enabled, that signature works correctly, provided the API never accepts a null argument. However, if the argument could be null, then the return value could also be null. To express that contract correctly, annotate this method as follows:

#nullable enable
[return: NotNullIfNotNull("url")]
string? GetTopLevelDomainFromFullUrl(string? url) { ... }

end example

22.5.7.10 The NotNullWhen attribute

Specifies that a nullable argument won’t be null when the method returns the specified bool value.

Example: The library method String.IsNullOrEmpty(String) returns true when the argument is null or an empty string. It’s a form of null-check: Callers don’t need to null-check the argument if the method returns false. To make a method like this nullable aware, make the parameter type a nullable reference type, and add the NotNullWhen attribute:

#nullable enable
bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }

end example

22.6 Attributes for interoperation

For interoperation with other languages, an indexer may be implemented using indexed properties. If no IndexerName attribute is present for an indexer, then the name Item is used by default. The IndexerName attribute enables a developer to override this default and specify a different name.

Example: By default, an indexer’s name is Item. This can be overridden, as follows:

[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    get { ... }
    set { ... }
}

Now, the indexer’s name is TheItem.

end example