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 theSimple
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, theAttribute
suffix may be omitted, resulting in the short nameSimple
. 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 theAuthor
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 propertyUrl
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 assemblymodule
— 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 delegatereturn
— 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 methodreturn
— 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 operatorreturn
— 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 methodreturn
— 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 methodparam
— 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 declarationfield
— the target is the fieldmethod
— 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 methodparam
— 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 fromSystem.Attribute
then the type is the result of this step.
- 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
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
andExampleAttribute
. The attribute[Example]
is ambiguous, since it could refer to eitherExample
orExampleAttribute
. 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 namedExampleAttributeAttribute
!). If the declaration for classExample
is removed, then both attributes refer to the attribute class namedExampleAttribute
, 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 ofClass1
.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:
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 constructorC
onT
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
inN
:- Let
Name
be the identifier of the named_argumentArg
. Name
shall identify a non-static read-write public field or property onT
. IfT
has no such field or property, then a compile-time error occurs.
- Let
- If any of the values within positional_argument_list
P
or one of the values within named_argument_listN
is of typeSystem.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 constructorC
onT
, the positional_argument_listP
, the named_argument_listN
, and the associated program entityE
, 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 constructorC
and values as determined at compile-time. These steps either result in an exception, or produce an instanceO
ofT
. - For each named_argument
Arg
inN
, in order:- Let
Name
be the identifier of the named_argumentArg
. IfName
does not identify a non-static public read-write field or property onO
, then an exception is thrown. - Let
Value
be the result of evaluating the attribute_argument_expression ofArg
. - If
Name
identifies a field onO
, then set this field toValue
. - Otherwise, Name identifies a property on
O
. Set this property to Value. - The result is
O
, an instance of the attribute classT
that has been initialized with the positional_argument_listP
and the named_argument_listN
.
- Let
Note: The format for storing
T
,C
,P
,N
(and associating it withE
) inA
and the mechanism to specifyE
and retrieveT
,C
,P
,N
fromA
(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), andSystem.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 symbolsALPHA
andBETA
.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 thevirtual
modifier, however. Overrides of such a method are implicitly conditional, and shall not be explicitly marked with aConditional
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
’sTest
method calls this method. Since the conditional compilation symbolDEBUG
is defined, ifClass2.Test
is called, it will callM
. If the symbolDEBUG
had not been defined, thenClass2.Test
would not callClass1.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
andClass3
each contain calls to the conditional methodClass1.F
, which is conditional based on whether or notDEBUG
is defined. Since this symbol is defined in the context ofClass2
but notClass3
, the call toF
inClass2
is included, while the call toF
inClass3
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 theM
defined in its base class. This call is omitted because the base method is conditional based on the presence of the symbolDEBUG
, 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 symbolsALPHA
andBETA
.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
andClass2
are each decorated with attributeTest
, which is conditional based on whether or notDEBUG
is defined. Since this symbol is defined in the context ofClass1
but notClass2
, the specification of the Test attribute onClass1
is included, while the specification of theTest
attribute onClass2
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, a compiler shall issue a warning or an error. Specifically, a compiler shall issue a warning if no error parameter is provided, or if the error parameter is provided and has the value false
. A 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 theObsolete
attribute. Each use ofA
inMain
results in a warning that includes the specified message, “This class is obsolete; use classB
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, a 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 a 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 a compiler in a number of ways. First, a compiler can issue a warning if there’s a path where the method can exit without throwing an exception. Second, a 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 bystring
,T?
becomes a nullable annotation. However, this code is not legal becauseT
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, thevalue
argument is guaranteed to be notnull
. However, it’s acceptable to callThrowWhenNull
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 a compiler’s analysis when a method always returns a non-null value when certain arguments are not
null
theNotNullIfNotNull
attribute may be used. Consider the following method:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
If the
url
argument isn’tnull
,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)
returnstrue
when the argument isnull
or an empty string. It’s a form of null-check: Callers don’t need to null-check the argument if the method returnsfalse
. 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
ECMA C# draft specification