Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
23.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
HelpAttributeattribute 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 (§23.2), which can have positional and named parameters (§23.2.3). Attributes are attached to entities in a C# program using attribute specifications (§23.3), and can be retrieved at run-time as attribute instances (§23.4).
23.2 Attribute classes
23.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 attributeend example
23.2.2 Attribute usage
The attribute AttributeUsage (§23.5.2) is used to describe how an attribute class can be used.
AttributeUsage has a positional parameter (§23.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
SimpleAttributethat can be placed on class_declarations and interface_declarations only, and shows several uses of theSimpleattribute.[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, theAttributesuffix 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 (§23.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
AuthorAttributeand shows a class declaration with two uses of theAuthorattribute:[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 (§23.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 { ... }
23.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
HelpAttributethat has one positional parameter,url, and one named parameter,Topic. Although it is non-static and public, the propertyUrldoes 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
23.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.
23.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 (§19.4), struct_member_declarations (§16.3), enum_member_declarations (§20.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
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)* ','?
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
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.Attributethen the type is the result of this step. - The characters
Attributeare 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.Attributethen 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
ExampleandExampleAttribute. The attribute[Example]is ambiguous, since it could refer to eitherExampleorExampleAttribute. 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 were an attribute class namedExampleAttributeAttribute!). If the declaration for classExampleis 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
Eis an attribute parameter type (§23.2.4). - At compile-time, the value of
Ecan 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.
23.4 Attribute instances
23.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.
23.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 constructorConTthat can be invoked at run-time. - If
Cdoes not have public accessibility, then a compile-time error occurs. - For each named_argument
ArginN:- Let
Namebe the identifier of the named_argumentArg. Nameshall identify a non-static read-write public field or property onT. IfThas no such field or property, then a compile-time error occurs.
- Let
- If any of the values within positional_argument_list
Por one of the values within named_argument_listNis of typeSystem.Stringand 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 (§23.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 constructorConT, the positional_argument_listP, the named_argument_listN, and the associated program entityE, with the values resolved completely at compile-time.
23.4.3 Run-time retrieval of an attribute instance
Using the terms defined in §23.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 constructorCand values as determined at compile-time. These steps either result in an exception, or produce an instanceOofT. - For each named_argument
ArginN, in order:- Let
Namebe the identifier of the named_argumentArg. IfNamedoes not identify a non-static public read-write field or property onO, then an exception is thrown. - Let
Valuebe the result of evaluating the attribute_argument_expression ofArg. - If
Nameidentifies 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 classTthat has been initialized with the positional_argument_listPand the named_argument_listN.
- Let
Note: The format for storing
T,C,P,N(and associating it withE) inAand the mechanism to specifyEand retrieveT,C,P,NfromA(and hence how an attribute instance is obtained at runtime) is beyond the scope of this specification. end note
23.5 Reserved attributes
23.5.1 General
A number of attributes affect the language in some way. These attributes include:
System.AttributeUsageAttribute(§23.5.2), which is used to describe the ways in which an attribute class can be used.System.Diagnostics.ConditionalAttribute(§23.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(§23.5.4), which is used to mark a member as obsolete.System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5), which is used to establish a task builder for an async method.System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3), andSystem.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4), which are used to supply information about the calling context to optional parameters.System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8), which is used to specify parameter for the cancellation token in an asynchronous iterator.
The Nullable static analysis attributes (§23.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.
23.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 §23.2.2. end note
23.5.3 The Conditional attribute
23.5.3.1 General
The attribute Conditional enables the definition of conditional methods and conditional attribute classes.
23.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.Mas a conditional method associated with the two conditional compilation symbolsALPHAandBETA.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
Conditionalattribute 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
overridemodifier. A conditional method can be marked with thevirtualmodifier, however. Overrides of such a method are implicitly conditional, and shall not be explicitly marked with aConditionalattribute. - 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.Mas a conditional method.Class2’sTestmethod calls this method. Since the conditional compilation symbolDEBUGis defined, ifClass2.Testis called, it will callM. If the symbolDEBUGhad not been defined, thenClass2.Testwould 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
Class2andClass3each contain calls to the conditional methodClass1.F, which is conditional based on whether or notDEBUGis defined. Since this symbol is defined in the context ofClass2but notClass3, the call toFinClass2is included, while the call toFinClass3is 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 } }
Class2includes a call to theMdefined 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
23.5.3.3 Conditional attribute classes
An attribute class (§23.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
TestAttributeas a conditional attribute class associated with the conditional compilations symbolsALPHAandBETA.end example
Attribute specifications (§23.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
Class1andClass2are each decorated with attributeTest, which is conditional based on whether or notDEBUGis defined. Since this symbol is defined in the context ofClass1but notClass2, the specification of the Test attribute onClass1is included, while the specification of theTestattribute onClass2is omitted.end example
23.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
Ais decorated with theObsoleteattribute. Each use ofAinMainresults in a warning that includes the specified message, “This class is obsolete; use classBinstead”.end example
23.5.5 The AsyncMethodBuilder attribute
This attribute is described in §15.14.1.
23.5.6 Caller-info attributes
23.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.
23.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).
23.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).
23.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 (§23.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.
23.5.7 Code analysis attributes
23.5.7.1 General
The attributes in this subclause 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 subclause 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 (§23.5.7.2) |
A non-nullable argument may be null. |
DisallowNull (§23.5.7.3) |
A nullable argument should never be null. |
MaybeNull (§23.5.7.6) |
A non-nullable return value may be null. |
NotNull (§23.5.7.8) |
A nullable return value will never be null. |
MaybeNullWhen (§23.5.7.7) |
A non-nullable argument may be null when the method returns the specified bool value. |
NotNullWhen (§23.5.7.10) |
A nullable argument won’t be null when the method returns the specified bool value. |
NotNullIfNotNull (§23.5.7.9) |
A return value isn’t null if the argument for the specified parameter isn’t null. |
DoesNotReturn (§23.5.7.4) |
This method never returns. |
DoesNotReturnIf (§23.5.7.5) |
This method never returns if the associated bool parameter has the specified value. |
The following subclauses in §23.5.7.1 are conditionally normative.
23.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
nullbecause 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 AllowNullwithout 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
23.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
23.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
23.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
23.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
Tis replaced bystring,T?becomes a nullable annotation. However, this code is not legal becauseTis 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
23.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 (§23.5.7.6), but includes a parameter for the specified return value.
23.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
ThrowWhenNullcompiles without warnings. When that method returns, thevalueargument is guaranteed to be notnull. However, it’s acceptable to callThrowWhenNullwith a null reference. end example
23.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
nulltheNotNullIfNotNullattribute may be used. Consider the following method:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }If the
urlargument isn’tnull,nullisn’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
23.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)returnstruewhen the argument isnullor 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
23.5.8 The EnumeratorCancellation attribute
Specifies the parameter representing the CancellationToken for an asynchronous iterator (§15.15). The argument for this parameter shall be combined with the argument passed to IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken). This combined token shall be polled by IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2). The tokens shall be combined into a single token as if by CancellationToken.CreateLinkedTokenSource and its Token property. The combined token will be canceled if either of the two source tokens are canceled. The combined token is seen as the argument to the asynchronous iterator method (§15.15) in the body of that method.
It is an error if the System.Runtime.CompilerServices.EnumeratorCancellation attribute is applied to more than one parameter. The compiler may produce a warning if:
- The
EnumeratorCancellationattribute is applied to a parameter of a type other thanCancellationToken, - or if the
EnumeratorCancellationattribute is applied to a parameter on a method that isn’t an asynchronous iterator (§15.15), - or if the
EnumeratorCancellationattribute is applied to a parameter on a method that returns an asynchronous enumerable interface (§15.15.3) rather than an asynchronous enumerator interface (§15.15.2).
The iterator won’t have access to the CancellationToken argument for GetAsyncEnumerator when no attributes have this parameter.
Example: The method
GetStringsAsync()is an asynchronous iterator. Before doing any work to retrieve the next value, it checks the cancellation token to determine if the iteration should be canceled. If cancellation is requested, no further action is taken.public static async Task ExampleCombination() { var sourceOne = new CancellationTokenSource(); var sourceTwo = new CancellationTokenSource(); await using (IAsyncEnumerator<string> enumerator = GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token)) { while (await enumerator.MoveNextAsync()) { string number = enumerator.Current; if (number == "8") sourceOne.Cancel(); if (number == "5") sourceTwo.Cancel(); Console.WriteLine(number); } } } static async IAsyncEnumerable<string> GetStringsAsync([EnumeratorCancellation] CancellationToken token) { for (int i = 0; i < 10; i++) { if (token.IsCancellationRequested) yield break; await Task.Delay(1000, token); yield return i.ToString(); } }end example
23.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