* Initial port of the C# 6 specification; all the heavy lifting had been done by @ljw1004 * Remove pdf, html versions and grammar versions until we figure out how to build them.
846 lines
39 KiB
Markdown
846 lines
39 KiB
Markdown
# Attributes
|
|
|
|
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_modifier*s `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. 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.
|
|
|
|
Attributes are defined through the declaration of attribute classes ([Attribute classes](attributes.md#attribute-classes)), which may have positional and named parameters ([Positional and named parameters](attributes.md#positional-and-named-parameters)). Attributes are attached to entities in a C# program using attribute specifications ([Attribute specification](attributes.md#attribute-specification)), and can be retrieved at run-time as attribute instances ([Attribute instances](attributes.md#attribute-instances)).
|
|
|
|
## Attribute classes
|
|
|
|
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 a declaration. By convention, attribute classes are named with a suffix of `Attribute`. Uses of an attribute may either include or omit this suffix.
|
|
|
|
### Attribute usage
|
|
|
|
The attribute `AttributeUsage` ([The AttributeUsage attribute](attributes.md#the-attributeusage-attribute)) is used to describe how an attribute class can be used.
|
|
|
|
`AttributeUsage` has a positional parameter ([Positional and named parameters](attributes.md#positional-and-named-parameters)) that enables an attribute class to specify the kinds of declarations on which it can be used. The example
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
|
|
public class SimpleAttribute: Attribute
|
|
{
|
|
...
|
|
}
|
|
```
|
|
|
|
defines an attribute class named `SimpleAttribute` that can be placed on *class_declaration*s and *interface_declaration*s only. The example
|
|
|
|
```csharp
|
|
[Simple] class Class1 {...}
|
|
|
|
[Simple] interface Interface1 {...}
|
|
```
|
|
|
|
shows several uses of the `Simple` attribute. Although this attribute is defined with the name `SimpleAttribute`, when this attribute is used, the `Attribute` suffix may be omitted, resulting in the short name `Simple`. Thus, the example above is semantically equivalent to the following:
|
|
|
|
```csharp
|
|
[SimpleAttribute] class Class1 {...}
|
|
|
|
[SimpleAttribute] interface Interface1 {...}
|
|
```
|
|
|
|
`AttributeUsage` has a named parameter ([Positional and named parameters](attributes.md#positional-and-named-parameters)) 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.
|
|
|
|
The example
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
|
public class AuthorAttribute: Attribute
|
|
{
|
|
private string name;
|
|
|
|
public AuthorAttribute(string name) {
|
|
this.name = name;
|
|
}
|
|
|
|
public string Name {
|
|
get { return name; }
|
|
}
|
|
}
|
|
```
|
|
|
|
defines a multi-use attribute class named `AuthorAttribute`. The example
|
|
|
|
```csharp
|
|
[Author("Brian Kernighan"), Author("Dennis Ritchie")]
|
|
class Class1
|
|
{
|
|
...
|
|
}
|
|
```
|
|
|
|
shows a class declaration with two uses of the `Author` attribute.
|
|
|
|
`AttributeUsage` has another named parameter 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
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
class X: Attribute {...}
|
|
```
|
|
|
|
is equivalent to the following:
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(
|
|
AttributeTargets.All,
|
|
AllowMultiple = false,
|
|
Inherited = true)
|
|
]
|
|
class X: Attribute {...}
|
|
```
|
|
|
|
### 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.
|
|
|
|
The example
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(AttributeTargets.Class)]
|
|
public class HelpAttribute: Attribute
|
|
{
|
|
public HelpAttribute(string url) { // Positional parameter
|
|
...
|
|
}
|
|
|
|
public string Topic { // Named parameter
|
|
get {...}
|
|
set {...}
|
|
}
|
|
|
|
public string Url {
|
|
get {...}
|
|
}
|
|
}
|
|
```
|
|
|
|
defines an attribute class named `HelpAttribute` that has one positional parameter, `url`, and one named parameter, `Topic`. Although it is non-static and public, the property `Url` does not define a named parameter, since it is not read-write.
|
|
|
|
This attribute class might be used as follows:
|
|
|
|
```csharp
|
|
[Help("http://www.mycompany.com/.../Class1.htm")]
|
|
class Class1
|
|
{
|
|
...
|
|
}
|
|
|
|
[Help("http://www.mycompany.com/.../Misc.htm", Topic = "Class2")]
|
|
class Class2
|
|
{
|
|
...
|
|
}
|
|
```
|
|
|
|
### 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`.
|
|
* An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility ([Attribute specification](attributes.md#attribute-specification)).
|
|
* Single-dimensional arrays of the above types.
|
|
* A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.
|
|
|
|
## Attribute specification
|
|
|
|
***Attribute specification*** is the application of a previously defined attribute to a declaration. An attribute is a piece of additional declarative information that is specified for a declaration. Attributes can be specified at global scope (to specify attributes on the containing assembly or module) and for *type_declaration*s ([Type declarations](namespaces.md#type-declarations)), *class_member_declaration*s ([Type parameter constraints](classes.md#type-parameter-constraints)), *interface_member_declaration*s ([Interface members](interfaces.md#interface-members)), *struct_member_declaration*s ([Struct members](structs.md#struct-members)), *enum_member_declaration*s ([Enum members](enums.md#enum-members)), *accessor_declarations* ([Accessors](classes.md#accessors)), *event_accessor_declarations* ([Field-like events](classes.md#field-like-events)), and *formal_parameter_list*s ([Method parameters](classes.md#method-parameters)).
|
|
|
|
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.
|
|
|
|
```antlr
|
|
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
|
|
: 'assembly'
|
|
| 'module'
|
|
;
|
|
|
|
attributes
|
|
: attribute_section+
|
|
;
|
|
|
|
attribute_section
|
|
: '[' attribute_target_specifier? attribute_list ']'
|
|
| '[' attribute_target_specifier? attribute_list ',' ']'
|
|
;
|
|
|
|
attribute_target_specifier
|
|
: attribute_target ':'
|
|
;
|
|
|
|
attribute_target
|
|
: 'field'
|
|
| 'event'
|
|
| 'method'
|
|
| 'param'
|
|
| 'property'
|
|
| 'return'
|
|
| 'type'
|
|
;
|
|
|
|
attribute_list
|
|
: attribute (',' attribute)*
|
|
;
|
|
|
|
attribute
|
|
: attribute_name attribute_arguments?
|
|
;
|
|
|
|
attribute_name
|
|
: type_name
|
|
;
|
|
|
|
attribute_arguments
|
|
: '(' positional_argument_list? ')'
|
|
| '(' positional_argument_list ',' named_argument_list ')'
|
|
| '(' named_argument_list ')'
|
|
;
|
|
|
|
positional_argument_list
|
|
: positional_argument (',' positional_argument)*
|
|
;
|
|
|
|
positional_argument
|
|
: attribute_argument_expression
|
|
;
|
|
|
|
named_argument_list
|
|
: named_argument (',' named_argument)*
|
|
;
|
|
|
|
named_argument
|
|
: identifier '=' attribute_argument_expression
|
|
;
|
|
|
|
attribute_argument_expression
|
|
: expression
|
|
;
|
|
```
|
|
|
|
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.
|
|
|
|
The *attribute_name* identifies an attribute class. If the form of *attribute_name* is *type_name* then this name must refer to an attribute class. Otherwise, a compile-time error occurs. The example
|
|
|
|
```csharp
|
|
class Class1 {}
|
|
|
|
[Class1] class Class2 {} // Error
|
|
```
|
|
|
|
results in a compile-time error because it attempts to use `Class1` as an attribute class when `Class1` is not an attribute class.
|
|
|
|
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*. When an attribute is placed at the global level, a *global_attribute_target_specifier* is required. In all other locations, a reasonable default is applied, but an *attribute_target_specifier* can be used to affirm or override the default in certain ambiguous cases (or to just affirm the default in non-ambiguous cases). Thus, typically, *attribute_target_specifier*s can be omitted except at the global level. The potentially ambiguous contexts are resolved as follows:
|
|
|
|
* An attribute specified at global scope can apply either to the target assembly or the target module. No default exists for this context, so an *attribute_target_specifier* is always required in this context. The presence of the `assembly` *attribute_target_specifier* indicates that the attribute applies to the target assembly; the presence of the `module` *attribute_target_specifier* indicates that the attribute applies to the target module.
|
|
* An attribute specified on a delegate declaration can apply either to the delegate being declared or to its return value. In the absence of an *attribute_target_specifier*, the attribute applies to the delegate. The presence of the `type` *attribute_target_specifier* indicates that the attribute applies to the delegate; the presence of the `return` *attribute_target_specifier* indicates that the attribute applies to the return value.
|
|
* An attribute specified on a method declaration can apply either to the method being declared or to its return value. In the absence of an *attribute_target_specifier*, the attribute applies to the method. The presence of the `method` *attribute_target_specifier* indicates that the attribute applies to the method; the presence of the `return` *attribute_target_specifier* indicates that the attribute applies to the return value.
|
|
* An attribute specified on an operator declaration can apply either to the operator being declared or to its return value. In the absence of an *attribute_target_specifier*, the attribute applies to the operator. The presence of the `method` *attribute_target_specifier* indicates that the attribute applies to the operator; the presence of the `return` *attribute_target_specifier* indicates that the attribute applies to the return value.
|
|
* An attribute specified on an event declaration that omits event accessors can apply to the event being declared, to the associated field (if the event is not abstract), or to the associated add and remove methods. In the absence of an *attribute_target_specifier*, the attribute applies to the event. The presence of the `event` *attribute_target_specifier* indicates that the attribute applies to the event; the presence of the `field` *attribute_target_specifier* indicates that the attribute applies to the field; and the presence of the `method` *attribute_target_specifier* indicates that the attribute applies to the methods.
|
|
* An attribute specified on a get accessor declaration for a property or indexer declaration can apply either to the associated method or to its return value. In the absence of an *attribute_target_specifier*, the attribute applies to the method. The presence of the `method` *attribute_target_specifier* indicates that the attribute applies to the method; the presence of the `return` *attribute_target_specifier* indicates that the attribute applies to the return value.
|
|
* An attribute specified on a set accessor for a property or indexer declaration can apply either to the associated method or to its lone implicit parameter. In the absence of an *attribute_target_specifier*, the attribute applies to the method. The presence of the `method` *attribute_target_specifier* indicates that the attribute applies to the method; the presence of the `param` *attribute_target_specifier* indicates that the attribute applies to the parameter; the presence of the `return` *attribute_target_specifier* indicates that the attribute applies to the return value.
|
|
* An attribute specified on an add or remove accessor declaration for an event declaration can apply either to the associated method or to its lone parameter. In the absence of an *attribute_target_specifier*, the attribute applies to the method. The presence of the `method` *attribute_target_specifier* indicates that the attribute applies to the method; the presence of the `param` *attribute_target_specifier* indicates that the attribute applies to the parameter; the presence of the `return` *attribute_target_specifier* indicates that the attribute applies to the return value.
|
|
|
|
In other contexts, inclusion of an *attribute_target_specifier* is permitted but unnecessary. For instance, a class declaration may either include or omit the specifier `type`:
|
|
|
|
```csharp
|
|
[type: Author("Brian Kernighan")]
|
|
class Class1 {}
|
|
|
|
[Author("Dennis Ritchie")]
|
|
class Class2 {}
|
|
```
|
|
|
|
It is an error to specify an invalid *attribute_target_specifier*. For instance, the specifier `param` cannot be used on a class declaration:
|
|
|
|
```csharp
|
|
[param: Author("Brian Kernighan")] // Error
|
|
class Class1 {}
|
|
```
|
|
|
|
By convention, attribute classes are named with a suffix of `Attribute`. An *attribute_name* of the form *type_name* may either include or omit this suffix. 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 ([Identifiers](lexical-structure.md#identifiers)), then only an attribute without a suffix is matched, thus enabling such an ambiguity to be resolved. The example
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(AttributeTargets.All)]
|
|
public class X: Attribute
|
|
{}
|
|
|
|
[AttributeUsage(AttributeTargets.All)]
|
|
public class XAttribute: Attribute
|
|
{}
|
|
|
|
[X] // Error: ambiguity
|
|
class Class1 {}
|
|
|
|
[XAttribute] // Refers to XAttribute
|
|
class Class2 {}
|
|
|
|
[@X] // Refers to X
|
|
class Class3 {}
|
|
|
|
[@XAttribute] // Refers to XAttribute
|
|
class Class4 {}
|
|
```
|
|
|
|
shows two attribute classes named `X` and `XAttribute`. The attribute `[X]` is ambiguous, since it could refer to either `X` or `XAttribute`. Using a verbatim identifier allows the exact intent to be specified in such rare cases. The attribute `[XAttribute]` is not ambiguous (although it would be if there was an attribute class named `XAttributeAttribute`!). If the declaration for class `X` is removed, then both attributes refer to the attribute class named `XAttribute`, as follows:
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(AttributeTargets.All)]
|
|
public class XAttribute: Attribute
|
|
{}
|
|
|
|
[X] // Refers to XAttribute
|
|
class Class1 {}
|
|
|
|
[XAttribute] // Refers to XAttribute
|
|
class Class2 {}
|
|
|
|
[@X] // Error: no attribute named "X"
|
|
class Class3 {}
|
|
```
|
|
|
|
It is a compile-time error to use a single-use attribute class more than once on the same entity. The example
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(AttributeTargets.Class)]
|
|
public class HelpStringAttribute: Attribute
|
|
{
|
|
string value;
|
|
|
|
public HelpStringAttribute(string value) {
|
|
this.value = value;
|
|
}
|
|
|
|
public string Value {
|
|
get {...}
|
|
}
|
|
}
|
|
|
|
[HelpString("Description of Class1")]
|
|
[HelpString("Another description of Class1")]
|
|
public class Class1 {}
|
|
```
|
|
|
|
results in a compile-time error because it attempts to use `HelpString`, which is a single-use attribute class, more than once on the declaration of `Class1`.
|
|
|
|
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 ([Attribute parameter types](attributes.md#attribute-parameter-types)).
|
|
* At compile-time, the value of `E` can be resolved to one of the following:
|
|
* A constant value.
|
|
* A `System.Type` object.
|
|
* A one-dimensional array of *attribute_argument_expression*s.
|
|
|
|
For example:
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
[AttributeUsage(AttributeTargets.Class)]
|
|
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 {}
|
|
```
|
|
|
|
A *typeof_expression* ([The typeof operator](expressions.md#the-typeof-operator)) used as an attribute argument expression can reference a non-generic type, a closed constructed type, or an unbound generic type, but it cannot reference an open type. This is to ensure that the expression can be resolved at compile-time.
|
|
|
|
```csharp
|
|
class A: Attribute
|
|
{
|
|
public A(Type t) {...}
|
|
}
|
|
|
|
class G<T>
|
|
{
|
|
[A(typeof(T))] T t; // Error, open type in attribute
|
|
}
|
|
|
|
class X
|
|
{
|
|
[A(typeof(List<int>))] int x; // Ok, closed constructed type
|
|
[A(typeof(List<>))] int y; // Ok, unbound generic type
|
|
}
|
|
```
|
|
|
|
## Attribute instances
|
|
|
|
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 sections.
|
|
|
|
### Compilation of an attribute
|
|
|
|
The compilation of an *attribute* with attribute class `T`, *positional_argument_list* `P` and *named_argument_list* `N`, consists of the following steps:
|
|
|
|
* Follow the compile-time processing steps for compiling an *object_creation_expression* of the form `new T(P)`. These steps either result in a compile-time error, or determine an instance constructor `C` on `T` that can be invoked at run-time.
|
|
* If `C` does not have public accessibility, then a compile-time error occurs.
|
|
* For each *named_argument* `Arg` in `N`:
|
|
* Let `Name` be the *identifier* of the *named_argument* `Arg`.
|
|
* `Name` must identify a non-static read-write public field or property on `T`. If `T` has no such field or property, then a compile-time error occurs.
|
|
* Keep the following information for run-time instantiation of the attribute: the attribute class `T`, the instance constructor `C` on `T`, the *positional_argument_list* `P` and the *named_argument_list* `N`.
|
|
|
|
### Run-time retrieval of an attribute instance
|
|
|
|
Compilation of an *attribute* yields an attribute class `T`, an instance constructor `C` on `T`, a *positional_argument_list* `P`, and a *named_argument_list* `N`. Given this information, an attribute instance can be retrieved at run-time using the following steps:
|
|
|
|
* Follow the run-time processing steps for executing an *object_creation_expression* of the form `new T(P)`, using the instance constructor `C` as determined at compile-time. These steps either result in an exception, or produce an instance `O` of `T`.
|
|
* For each *named_argument* `Arg` in `N`, in order:
|
|
* Let `Name` be the *identifier* of the *named_argument* `Arg`. If `Name` does not identify a non-static public read-write field or property on `O`, then an exception is thrown.
|
|
* Let `Value` be the result of evaluating the *attribute_argument_expression* of `Arg`.
|
|
* If `Name` identifies a field on `O`, then set this field to `Value`.
|
|
* Otherwise, `Name` identifies a property on `O`. Set this property to `Value`.
|
|
* The result is `O`, an instance of the attribute class `T` that has been initialized with the *positional_argument_list* `P` and the *named_argument_list* `N`.
|
|
|
|
## Reserved attributes
|
|
|
|
A small number of attributes affect the language in some way. These attributes include:
|
|
|
|
* `System.AttributeUsageAttribute` ([The AttributeUsage attribute](attributes.md#the-attributeusage-attribute)), which is used to describe the ways in which an attribute class can be used.
|
|
* `System.Diagnostics.ConditionalAttribute` ([The Conditional attribute](attributes.md#the-conditional-attribute)), which is used to define conditional methods.
|
|
* `System.ObsoleteAttribute` ([The Obsolete attribute](attributes.md#the-obsolete-attribute)), which is used to mark a member as obsolete.
|
|
* `System.Runtime.CompilerServices.CallerLineNumberAttribute`, `System.Runtime.CompilerServices.CallerFilePathAttribute` and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([Caller info attributes](attributes.md#caller-info-attributes)), which are used to supply information about the calling context to optional parameters.
|
|
|
|
### 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 must derive from `System.Attribute`, either directly or indirectly. Otherwise, a compile-time error occurs.
|
|
|
|
```csharp
|
|
namespace System
|
|
{
|
|
[AttributeUsage(AttributeTargets.Class)]
|
|
public class AttributeUsageAttribute: Attribute
|
|
{
|
|
public AttributeUsageAttribute(AttributeTargets validOn) {...}
|
|
public virtual bool AllowMultiple { get {...} set {...} }
|
|
public virtual bool Inherited { get {...} set {...} }
|
|
public virtual AttributeTargets ValidOn { get {...} }
|
|
}
|
|
|
|
public enum AttributeTargets
|
|
{
|
|
Assembly = 0x0001,
|
|
Module = 0x0002,
|
|
Class = 0x0004,
|
|
Struct = 0x0008,
|
|
Enum = 0x0010,
|
|
Constructor = 0x0020,
|
|
Method = 0x0040,
|
|
Property = 0x0080,
|
|
Field = 0x0100,
|
|
Event = 0x0200,
|
|
Interface = 0x0400,
|
|
Parameter = 0x0800,
|
|
Delegate = 0x1000,
|
|
ReturnValue = 0x2000,
|
|
|
|
All = Assembly | Module | Class | Struct | Enum | Constructor |
|
|
Method | Property | Field | Event | Interface | Parameter |
|
|
Delegate | ReturnValue
|
|
}
|
|
}
|
|
```
|
|
|
|
### The Conditional attribute
|
|
|
|
The attribute `Conditional` enables the definition of ***conditional methods*** and ***conditional attribute classes***.
|
|
|
|
```csharp
|
|
namespace System.Diagnostics
|
|
{
|
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
|
|
public class ConditionalAttribute: Attribute
|
|
{
|
|
public ConditionalAttribute(string conditionString) {...}
|
|
public string ConditionString { get {...} }
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Conditional methods
|
|
|
|
A method decorated with the `Conditional` attribute is a conditional method. The `Conditional` attribute indicates a condition by testing a conditional compilation symbol. Calls to a conditional method are either included or omitted depending on whether this symbol is defined at the point of the call. If the symbol is defined, the call is included; otherwise, the call (including evaluation of the receiver and parameters of the call) is omitted.
|
|
|
|
A conditional method is subject to the following restrictions:
|
|
|
|
* The conditional method must 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 must have a return type of `void`.
|
|
* The conditional method must not be marked with the `override` modifier. A conditional method may be marked with the `virtual` modifier, however. Overrides of such a method are implicitly conditional, and must not be explicitly marked with a `Conditional` attribute.
|
|
* The conditional method must not be an implementation of an interface method. Otherwise, a compile-time error occurs.
|
|
|
|
In addition, a compile-time error occurs if a conditional method is used in a *delegate_creation_expression*. The example
|
|
|
|
```csharp
|
|
#define DEBUG
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
|
|
class Class1
|
|
{
|
|
[Conditional("DEBUG")]
|
|
public static void M() {
|
|
Console.WriteLine("Executed Class1.M");
|
|
}
|
|
}
|
|
|
|
class Class2
|
|
{
|
|
public static void Test() {
|
|
Class1.M();
|
|
}
|
|
}
|
|
```
|
|
|
|
declares `Class1.M` as a conditional method. `Class2`'s `Test` method calls this method. Since the conditional compilation symbol `DEBUG` is defined, if `Class2.Test` is called, it will call `M`. If the symbol `DEBUG` had not been defined, then `Class2.Test` would not call `Class1.M`.
|
|
|
|
It is important to note 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. In the example
|
|
|
|
File `class1.cs`:
|
|
|
|
```csharp
|
|
using System.Diagnostics;
|
|
|
|
class Class1
|
|
{
|
|
[Conditional("DEBUG")]
|
|
public static void F() {
|
|
Console.WriteLine("Executed Class1.F");
|
|
}
|
|
}
|
|
```
|
|
|
|
File `class2.cs`:
|
|
|
|
```csharp
|
|
#define DEBUG
|
|
|
|
class Class2
|
|
{
|
|
public static void G() {
|
|
Class1.F(); // F is called
|
|
}
|
|
}
|
|
```
|
|
|
|
File `class3.cs`:
|
|
|
|
```csharp
|
|
#undef DEBUG
|
|
|
|
class Class3
|
|
{
|
|
public static void H() {
|
|
Class1.F(); // F is not called
|
|
}
|
|
}
|
|
```
|
|
|
|
the classes `Class2` and `Class3` each contain calls to the conditional method `Class1.F`, which is conditional based on whether or not `DEBUG` is defined. Since this symbol is defined in the context of `Class2` but not `Class3`, the call to `F` in `Class2` is included, while the call to `F` in `Class3` is omitted.
|
|
|
|
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. In the example
|
|
|
|
File `class1.cs`:
|
|
|
|
```csharp
|
|
using System;
|
|
using System.Diagnostics;
|
|
|
|
class Class1
|
|
{
|
|
[Conditional("DEBUG")]
|
|
public virtual void M() {
|
|
Console.WriteLine("Class1.M executed");
|
|
}
|
|
}
|
|
```
|
|
|
|
File `class2.cs`:
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
class Class2: Class1
|
|
{
|
|
public override void M() {
|
|
Console.WriteLine("Class2.M executed");
|
|
base.M(); // base.M is not called!
|
|
}
|
|
}
|
|
```
|
|
|
|
File `class3.cs`:
|
|
|
|
```csharp
|
|
#define DEBUG
|
|
|
|
using System;
|
|
|
|
class Class3
|
|
{
|
|
public static void Test() {
|
|
Class2 c = new Class2();
|
|
c.M(); // M is called
|
|
}
|
|
}
|
|
```
|
|
|
|
`Class2` includes a call to the `M` defined in its base class. This call is omitted because the base method is conditional based on the presence of the symbol `DEBUG`, which is undefined. Thus, the method writes to the console "`Class2.M executed`" only. Judicious use of *pp_declaration*s can eliminate such problems.
|
|
|
|
#### Conditional attribute classes
|
|
|
|
An attribute class ([Attribute classes](attributes.md#attribute-classes)) 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. This example:
|
|
|
|
```csharp
|
|
using System;
|
|
using System.Diagnostics;
|
|
[Conditional("ALPHA")]
|
|
[Conditional("BETA")]
|
|
public class TestAttribute : Attribute {}
|
|
```
|
|
|
|
declares `TestAttribute` as a conditional attribute class associated with the conditional compilations symbols `ALPHA` and `BETA`.
|
|
|
|
Attribute specifications ([Attribute specification](attributes.md#attribute-specification)) 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. In the example
|
|
|
|
File `test.cs`:
|
|
|
|
```csharp
|
|
using System;
|
|
using System.Diagnostics;
|
|
|
|
[Conditional("DEBUG")]
|
|
|
|
public class TestAttribute : Attribute {}
|
|
```
|
|
|
|
File `class1.cs`:
|
|
|
|
```csharp
|
|
#define DEBUG
|
|
|
|
[Test] // TestAttribute is specified
|
|
|
|
class Class1 {}
|
|
```
|
|
|
|
File `class2.cs`:
|
|
|
|
```csharp
|
|
#undef DEBUG
|
|
|
|
[Test] // TestAttribute is not specified
|
|
|
|
class Class2 {}
|
|
```
|
|
|
|
the classes `Class1` and `Class2` are each decorated with attribute `Test`, which is conditional based on whether or not `DEBUG` is defined. Since this symbol is defined in the context of `Class1` but not `Class2`, the specification of the `Test` attribute on `Class1` is included, while the specification of the `Test` attribute on `Class2` is omitted.
|
|
|
|
### The Obsolete attribute
|
|
|
|
The attribute `Obsolete` is used to mark types and members of types that should no longer be used.
|
|
|
|
```csharp
|
|
namespace System
|
|
{
|
|
[AttributeUsage(
|
|
AttributeTargets.Class |
|
|
AttributeTargets.Struct |
|
|
AttributeTargets.Enum |
|
|
AttributeTargets.Interface |
|
|
AttributeTargets.Delegate |
|
|
AttributeTargets.Method |
|
|
AttributeTargets.Constructor |
|
|
AttributeTargets.Property |
|
|
AttributeTargets.Field |
|
|
AttributeTargets.Event,
|
|
Inherited = false)
|
|
]
|
|
public class ObsoleteAttribute: Attribute
|
|
{
|
|
public ObsoleteAttribute() {...}
|
|
public ObsoleteAttribute(string message) {...}
|
|
public ObsoleteAttribute(string message, bool error) {...}
|
|
public string Message { get {...} }
|
|
public bool IsError { get {...} }
|
|
}
|
|
}
|
|
```
|
|
|
|
If a program uses a type or member that is decorated with the `Obsolete` attribute, the compiler issues a warning or an error. Specifically, the compiler issues a warning if no error parameter is provided, or if the error parameter is provided and has the value `false`. The compiler issues an error if the error parameter is specified and has the value `true`.
|
|
|
|
In the example
|
|
|
|
```csharp
|
|
[Obsolete("This class is obsolete; use class B instead")]
|
|
class A
|
|
{
|
|
public void F() {}
|
|
}
|
|
|
|
class B
|
|
{
|
|
public void F() {}
|
|
}
|
|
|
|
class Test
|
|
{
|
|
static void Main() {
|
|
A a = new A(); // Warning
|
|
a.F();
|
|
}
|
|
}
|
|
```
|
|
|
|
the class `A` is decorated with the `Obsolete` attribute. Each use of `A` in `Main` results in a warning that includes the specified message, "This class is obsolete; use class B instead."
|
|
|
|
### Caller info attributes
|
|
|
|
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.
|
|
|
|
For example:
|
|
|
|
```csharp
|
|
using System.Runtime.CompilerServices
|
|
|
|
...
|
|
|
|
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.
|
|
|
|
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 ([Overload resolution](expressions.md#overload-resolution)).
|
|
|
|
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 preferred in the following order: `CallerLineNumber`, `CallerFilePath`, `CallerMemberName`.
|
|
|
|
#### The CallerLineNumber attribute
|
|
|
|
The `System.Runtime.CompilerServices.CallerLineNumberAttribute` is allowed on optional parameters when there is a standard implicit conversion ([Standard implicit conversions](conversions.md#standard-implicit-conversions)) 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.
|
|
|
|
Note that the line number may be affected by `#line` directives ([Line directives](lexical-structure.md#line-directives)).
|
|
|
|
#### The CallerFilePath attribute
|
|
|
|
The `System.Runtime.CompilerServices.CallerFilePathAttribute` is allowed on optional parameters when there is a standard implicit conversion ([Standard implicit conversions](conversions.md#standard-implicit-conversions)) 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.
|
|
|
|
Note that the file path may be affected by `#line` directives ([Line directives](lexical-structure.md#line-directives)).
|
|
|
|
#### The CallerMemberName attribute
|
|
|
|
The `System.Runtime.CompilerServices.CallerMemberNameAttribute` is allowed on optional parameters when there is a standard implicit conversion ([Standard implicit conversions](conversions.md#standard-implicit-conversions)) 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` ([The IndexerName attribute](attributes.md#the-indexername-attribute)) on the indexer member, if present, or the default name `Item` otherwise.
|
|
|
|
For invocations that occur within declarations of instance constructors, static constructors, destructors and operators the member name used is implementation-dependent.
|
|
|
|
## Attributes for Interoperation
|
|
|
|
Note: This section is applicable only to the Microsoft .NET implementation of C#.
|
|
|
|
### Interoperation with COM and Win32 components
|
|
|
|
The .NET run-time provides a large number of attributes that enable C# programs to interoperate with components written using COM and Win32 DLLs. For example, the `DllImport` attribute can be used on a `static extern` method to indicate that the implementation of the method is to be found in a Win32 DLL. These attributes are found in the `System.Runtime.InteropServices` namespace, and detailed documentation for these attributes is found in the .NET runtime documentation.
|
|
|
|
### Interoperation with other .NET languages
|
|
|
|
#### The IndexerName attribute
|
|
|
|
Indexers are implemented in .NET using indexed properties, and have a name in the .NET metadata. 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.
|
|
|
|
```csharp
|
|
namespace System.Runtime.CompilerServices.CSharp
|
|
{
|
|
[AttributeUsage(AttributeTargets.Property)]
|
|
public class IndexerNameAttribute: Attribute
|
|
{
|
|
public IndexerNameAttribute(string indexerName) {...}
|
|
public string Value { get {...} }
|
|
}
|
|
}
|
|
```
|