22 Atributos
22.1 General
Gran parte del lenguaje C# permite al programador especificar información declarativa sobre las entidades definidas en el programa. Por ejemplo, la accesibilidad de un método de una clase se especifica decorando con el method_modifiers public
, protected
, internal
y private
.
C# permite a los programadores inventar nuevos tipos de información declarativa, denominados atributos. A continuación, los programadores pueden adjuntar atributos a varias entidades de programa y recuperar información de atributos en un entorno en tiempo de ejecución.
Nota: Por ejemplo, un marco podría definir un
HelpAttribute
atributo que se puede colocar en determinados elementos de programa (como clases y métodos) para proporcionar una asignación de esos elementos de programa a su documentación. nota final
Los atributos se definen mediante la declaración de clases de atributo (§22.2), que pueden tener parámetros posicionales y con nombre (§22.2.3). Los atributos se adjuntan a entidades de un programa de C# mediante especificaciones de atributo (§22.3) y se pueden recuperar en tiempo de ejecución como instancias de atributo (§22.4).
22.2 Clases de atributos
22.2.1 General
Una clase que deriva de la clase System.Attribute
abstracta , ya sea directa o indirectamente, es una clase de atributo. La declaración de una clase de atributo define un nuevo tipo de atributo que se puede colocar en entidades de programa. Por convención, las clases de atributo se denominan con un sufijo de Attribute
. Los usos de un atributo pueden incluir o omitir este sufijo.
Una declaración de clase genérica no se usará System.Attribute
como una clase base directa o indirecta.
Ejemplo:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute
ejemplo final
22.2.2 Uso de atributos
El atributo AttributeUsage
(§22.5.2) se usa para describir cómo se puede usar una clase de atributo.
AttributeUsage
tiene un parámetro posicional (§22.2.3) que permite a una clase de atributo especificar los tipos de entidades de programa en las que se puede usar.
Ejemplo: En el ejemplo siguiente se define una clase de atributo denominada
SimpleAttribute
que se puede colocar solo en class_declarations y interface_declarations, y muestra varios usos delSimple
atributo.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}
Aunque este atributo se define con el nombre
SimpleAttribute
, cuando se usa este atributo, se puede omitir elAttribute
sufijo, lo que da como resultado el nombreSimple
corto . Por lo tanto, el ejemplo anterior es semánticamente equivalente a lo siguiente[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}
ejemplo final
AttributeUsage
tiene un parámetro con nombre (§22.2.3), denominado AllowMultiple
, que indica si el atributo se puede especificar más de una vez para una entidad determinada. Si AllowMultiple
para una clase de atributo es true, esa clase de atributo es una clase de atributo de varios usos y se puede especificar más de una vez en una entidad. Si AllowMultiple
para una clase de atributo es false o no se especifica, esa clase de atributo es una clase de atributo de uso único y se puede especificar como máximo una vez en una entidad.
Ejemplo: en el ejemplo siguiente se define una clase de atributo de varios usos denominada
AuthorAttribute
y se muestra una declaración de clase con dos usos delAuthor
atributo :[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 { ... }
ejemplo final
AttributeUsage
tiene otro parámetro con nombre (§22.2.3), denominado Inherited
, que indica si el atributo, cuando se especifica en una clase base, también lo heredan las clases que derivan de esa clase base. Si Inherited
para una clase de atributo es true, ese atributo se hereda. Si Inherited
para una clase de atributo es false, ese atributo no se hereda. Si no se especifica, su valor predeterminado es true.
Una clase X
de atributo que no tiene un AttributeUsage
atributo asociado, como en
class X : Attribute { ... }
es equivalente a lo siguiente:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
22.2.3 Parámetros posicionales y con nombre
Las clases de atributo pueden tener parámetrosposicionales y parámetroscon nombre. Cada constructor de instancia pública de una clase de atributo define una secuencia válida de parámetros posicionales para esa clase de atributo. Cada campo de lectura y escritura público no estático y propiedad de una clase de atributo define un parámetro con nombre para la clase de atributo. Para que una propiedad defina un parámetro con nombre, esa propiedad tendrá un descriptor de acceso get público y un descriptor de acceso de conjunto público.
Ejemplo: en el ejemplo siguiente se define una clase de atributo denominada
HelpAttribute
que tiene un parámetro posicional,url
y un parámetro con nombre,Topic
. Aunque no es estático y público, la propiedadUrl
no define un parámetro con nombre, ya que no es de lectura y escritura. También se muestran dos usos de este atributo:[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 { }
ejemplo final
22.2.4 Tipos de parámetros de atributo
Los tipos de parámetros posicionales y con nombre para una clase de atributo se limitan a los tipos de parámetro de atributo, que son:
- Uno de los siguientes tipos:
bool
, ,char
float
double
byte
short
long
int
sbyte
, ,string
,uint
, ,ulong
, .ushort
- El tipo de la clase
object
. - El tipo de la clase
System.Type
. - Tipos de enumeración.
- Matrices unidimensionales de los tipos anteriores.
- Un argumento de constructor o un campo público que no tenga uno de estos tipos, no se usará como parámetro posicional o con nombre en una especificación de atributo.
Especificación de atributo 22.3
La especificación de atributo es la aplicación de un atributo definido previamente a una entidad de programa. Un atributo es un fragmento de información declarativa adicional que se especifica para una entidad de programa. Los atributos se pueden especificar en el ámbito global (para especificar atributos en el ensamblado o módulo contenedor) y para type_declaration s (§14.7), class_member_declarations (§15.3), interface_member_declarations (§15.3) 18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ declaracións (§15.8), elementos de parameter_lists (§15.6.2) y elementos de type_parameter_lists (§15.2.3).
Los atributos se especifican en las secciones de atributos. Una sección de atributo consta de un par de corchetes, que rodean una lista separada por comas de uno o varios atributos. El orden en el que se especifican los atributos en dicha lista y el orden en que se organizan las secciones adjuntas a la misma entidad de programa, no es significativo. Por ejemplo, las especificaciones de atributo , [B][A]
, [A, B]
y [B, A]
son equivalentes[A][B]
.
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
;
Para el global_attribute_target de producción y, en el texto siguiente, el identificador tendrá una ortografía igual a assembly
o module
, donde la igualdad se define en §6.4.3. Para el attribute_target de producción y, en el texto siguiente, el identificador tendrá una ortografía que no es igual a assembly
o module
, utilizando la misma definición de igualdad que antes.
Un atributo consta de una attribute_name y una lista opcional de argumentos posicionales y con nombre. Los argumentos posicionales (si los hay) preceden a los argumentos con nombre. Un argumento posicional consta de un attribute_argument_expression; un argumento con nombre consta de un nombre, seguido de un signo igual, seguido de un attribute_argument_expression, que, juntos, están restringidos por las mismas reglas que la asignación simple. El orden de los argumentos con nombre no es significativo.
Nota: Para mayor comodidad, se permite una coma final en un global_attribute_section y una attribute_section, al igual que se permite una en un array_initializer (§17.7). nota final
El attribute_name identifica una clase de atributo.
Cuando se coloca un atributo en el nivel global, se requiere un global_attribute_target_specifier . Cuando el global_attribute_target es igual a:
assembly
: el destino es el ensamblado contenedor.module
: el destino es el módulo contenedor.
No se permiten otros valores para global_attribute_target .
Los nombres de attribute_target estandarizados son event
, field
, method
param
, property
, , return
, type
y typevar
. Estos nombres de destino solo se usarán en los siguientes contextos:
event
— un evento.field
— un campo. Un evento similar a un campo (es decir, uno sin descriptores de acceso) (§15.8.2) y una propiedad implementada automáticamente (§15.7.4) también puede tener un atributo con este destino.method
: constructor, finalizador, método, operador, descriptores de acceso get y set de propiedades, descriptores de acceso get y set del indexador, y descriptores de acceso para agregar y quitar eventos. Un evento similar a un campo (es decir, uno sin descriptores de acceso) también puede tener un atributo con este destino.param
: un descriptor de acceso de conjunto de propiedades, un descriptor de acceso del conjunto de indizadores, un descriptor de acceso para agregar y quitar descriptores de acceso y un parámetro en un constructor, método y operador.property
: una propiedad y un indexador.return
: un descriptor de acceso get de delegado, método, operador, propiedad get e indizador get.type
: delegado, clase, estructura, enumeración e interfaz.typevar
: un parámetro de tipo.
Algunos contextos permiten la especificación de un atributo en más de un destino. Un programa puede especificar explícitamente el destino mediante la inclusión de un attribute_target_specifier. Sin un attribute_target_specifier se aplica un valor predeterminado, pero se puede usar un attribute_target_specifier para afirmar o invalidar el valor predeterminado. Los contextos se resuelven de la siguiente manera:
- Para un atributo en una declaración de delegado, el destino predeterminado es el delegado. De lo contrario, cuando el attribute_target es igual a:
type
: el destino es el delegado.return
: el destino es el valor devuelto.
- Para un atributo en una declaración de método, el destino predeterminado es el método . De lo contrario, cuando el attribute_target es igual a:
method
: el destino es el método .return
: el destino es el valor devuelto.
- Para un atributo en una declaración de operador, el destino predeterminado es el operador . De lo contrario, cuando el attribute_target es igual a:
method
: el destino es el operador .return
: el destino es el valor devuelto.
- Para un atributo en una declaración de descriptor de acceso get para una declaración de propiedad o indexador, el destino predeterminado es el método asociado. De lo contrario, cuando el attribute_target es igual a:
method
: el destino es el método asociado.return
: el destino es el valor devuelto.
- Para un atributo especificado en un descriptor de acceso set para una propiedad o declaración de indexador, el destino predeterminado es el método asociado. De lo contrario, cuando el attribute_target es igual a:
method
: el destino es el método asociado.param
: el destino es el parámetro implícito lone.
- Para un atributo en una declaración de propiedad implementada automáticamente, el destino predeterminado es la propiedad . De lo contrario, cuando el attribute_target es igual a:
field
: el destino es el campo de respaldo generado por el compilador para la propiedad .
- Para un atributo especificado en una declaración de evento que omite event_accessor_declarations el destino predeterminado es la declaración de evento. De lo contrario, cuando el attribute_target es igual a:
event
: el destino es la declaración de eventos.field
: el destino es el campo.method
: los destinos son los métodos.
- En el caso de una declaración de evento que no omite event_accessor_declarations el destino predeterminado es el método .
method
: el destino es el método asociado.param
: el destino es el parámetro lone.
En todos los demás contextos, se permite la inclusión de un attribute_target_specifier , pero no es necesario.
Ejemplo: una declaración de clase puede incluir o omitir el especificador
type
:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}
ejemplo final.
Una implementación puede aceptar otros attribute_targets, los propósitos de los cuales se definen. Una implementación que no reconozca dicha attribute_target emitirá una advertencia y omitirá la attribute_section contenedora.
Por convención, las clases de atributo se denominan con un sufijo de Attribute
. Un attribute_name puede incluir o omitir este sufijo. En concreto, se resuelve un attribute_name de la siguiente manera:
- Si el identificador más derecho del attribute_name es un identificador textual (§6.4.3), el attribute_name se resuelve como un type_name (§7.8). Si el resultado no es un tipo derivado de
System.Attribute
, se produce un error en tiempo de compilación. - En caso contrario,
- El attribute_name se resuelve como un type_name (§7.8), excepto que se suprimen los errores. Si esta resolución es correcta y da como resultado un tipo derivado de
System.Attribute
, el tipo es el resultado de este paso. - Los caracteres
Attribute
se anexan al identificador más derecho de la attribute_name y la cadena resultante de tokens se resuelve como una type_name (§7.8), excepto que se suprimen los errores. Si esta resolución es correcta y da como resultado un tipo derivado deSystem.Attribute
, el tipo es el resultado de este paso.
- El attribute_name se resuelve como un type_name (§7.8), excepto que se suprimen los errores. Si esta resolución es correcta y da como resultado un tipo derivado de
Si exactamente uno de los dos pasos anteriores da como resultado un tipo derivado de System.Attribute
, ese tipo es el resultado de la attribute_name. De lo contrario, se produce un error en tiempo de compilación.
Ejemplo: si se encuentra una clase de atributo con y sin este sufijo, existe una ambigüedad y se produce un error en tiempo de compilación. Si el attribute_name está escrito de forma que su identificador más derecho es un identificador textual (§6.4.3), solo se coincide con un atributo sin un sufijo, lo que permite resolver dicha ambigüedad. En el ejemplo
[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 {}
muestra dos clases de atributo denominadas
Example
yExampleAttribute
. El atributo[Example]
es ambiguo, ya que podría hacer referencia aExample
oExampleAttribute
. El uso de un identificador textual permite especificar la intención exacta en tales casos poco frecuentes. El atributo[ExampleAttribute]
no es ambiguo (aunque sería si hubiera una clase de atributo denominadaExampleAttributeAttribute
!). Si se quita la declaración de la claseExample
, ambos atributos hacen referencia a la clase de atributo denominadaExampleAttribute
, como se indica a continuación:[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 {}
ejemplo final
Se trata de un error en tiempo de compilación para usar una clase de atributo de uso único más de una vez en la misma entidad.
Ejemplo: El ejemplo
[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 {}
produce un error en tiempo de compilación porque intenta usar
HelpString
, que es una clase de atributo de uso único, más de una vez en la declaración deClass1
.ejemplo final
Una expresión E
es un attribute_argument_expression si se cumplen todas las instrucciones siguientes:
- El tipo de es un tipo de
E
parámetro de atributo (§22.2.4). - En tiempo de compilación, el valor de
E
se puede resolver en uno de los siguientes elementos:
Ejemplo:
[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; }
ejemplo final
Los atributos de un tipo declarado en varias partes se determinan combinando, en un orden no especificado, los atributos de cada una de sus partes. Si el mismo atributo se coloca en varias partes, equivale a especificar ese atributo varias veces en el tipo.
Ejemplo: las dos partes:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}
son equivalentes a la siguiente declaración única:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}
ejemplo final
Los atributos de los parámetros de tipo se combinan de la misma manera.
22.4 Instancias de atributo
22.4.1 General
Una instancia de atributo es una instancia que representa un atributo en tiempo de ejecución. Un atributo se define con una clase de atributo, argumentos posicionales y argumentos con nombre. Una instancia de atributo es una instancia de la clase de atributo que se inicializa con los argumentos posicionales y con nombre.
La recuperación de una instancia de atributo implica el procesamiento en tiempo de compilación y en tiempo de ejecución, como se describe en las subclases siguientes.
22.4.2 Compilación de un atributo
La compilación de un atributo con la clase T
de atributo , positional_argument_list P
, named_argument_listN
y se especifica en una entidad E
de programa se compila en un ensamblado A
mediante los pasos siguientes:
- Siga los pasos de procesamiento en tiempo de compilación para compilar una object_creation_expression del formulario nuevo
T(P)
. Estos pasos producen un error en tiempo de compilación o determinan un constructorC
de instancia enT
el que se puede invocar en tiempo de ejecución. - Si
C
no tiene accesibilidad pública, se produce un error en tiempo de compilación. - Para cada named_argument
Arg
enN
:- Vamos
Name
a ser el identificador del named_argumentArg
. Name
identificará un campo público de lectura y escritura no estático o una propiedad enT
. SiT
no tiene este campo o propiedad, se produce un error en tiempo de compilación.
- Vamos
- Si alguno de los valores de positional_argument_list
P
o uno de los valores de named_argument_listN
es de tipoSystem.String
y el valor no tiene el formato definido por el estándar Unicode, se define si el valor compilado es igual al valor en tiempo de ejecución recuperado (§22.4.3).Nota: Por ejemplo, una cadena que contiene una unidad de código UTF-16 suplente alta que no va seguida inmediatamente de una unidad de código suplente baja no tiene un formato correcto. nota final
- Almacene la siguiente información (para la creación de instancias en tiempo de ejecución del atributo) en la salida del ensamblado por el compilador como resultado de compilar el programa que contiene el atributo: la clase
T
de atributo , el constructorC
de instancia enT
, el positional_argument_listP
, el named_argument_listN
y la entidadE
de programa asociada , con los valores resueltos completamente en tiempo de compilación.
22.4.3 Recuperación en tiempo de ejecución de una instancia de atributo
Con los términos definidos en §22.4.2, la instancia de atributo representada por T
, C
, P
y N
, y asociada a E
se puede recuperar en tiempo de ejecución del ensamblado A
mediante los pasos siguientes:
- Siga los pasos de procesamiento en tiempo de ejecución para ejecutar una object_creation_expression del formulario
new T(P)
mediante el constructorC
de instancia y los valores que se determinan en tiempo de compilación. Estos pasos dan como resultado una excepción o generan una instanciaO
deT
. - Para cada named_argument
Arg
enN
, en orden:- Vamos
Name
a ser el identificador del named_argumentArg
. SiName
no identifica un campo o propiedad de lectura y escritura públicos no estáticos enO
, se produce una excepción. - Vamos
Value
a ser el resultado de evaluar la attribute_argument_expression deArg
. - Si
Name
identifica un campo enO
, establezca este campoValue
en . - De lo contrario, Name identifica una propiedad en
O
. Establezca esta propiedad en Value. - El resultado es
O
, una instancia de la claseT
de atributo que se ha inicializado con el positional_argument_listP
y el named_argument_listN
.
- Vamos
Nota: El formato para almacenar
T
,C
,P
,N
(y asociarlo aE
) enA
y el mecanismo para especificarE
y recuperarT
,C
, ,P
deA
(y, por tanto,N
cómo se obtiene una instancia de atributo en tiempo de ejecución) está fuera del ámbito de esta especificación. nota final
Ejemplo: en una implementación de la CLI, las
Help
instancias de atributo del ensamblado creado mediante la compilación del programa de ejemplo en §22.2.3 se pueden recuperar con el siguiente programa: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}"); } } } }
ejemplo final
22.5 Atributos reservados
22.5.1 General
Varios atributos afectan al idioma de alguna manera. Estos atributos son los siguientes:
System.AttributeUsageAttribute
(§22.5.2), que se usa para describir las formas en que se puede usar una clase de atributo.System.Diagnostics.ConditionalAttribute
(§22.5.3), es una clase de atributo de varios usos que se usa para definir métodos condicionales y clases de atributos condicionales. Este atributo indica una condición probando un símbolo de compilación condicional.System.ObsoleteAttribute
(§22.5.4), que se usa para marcar un miembro como obsoleto.System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
(§22.5.5), que se usa para establecer un generador de tareas para un método asincrónico.System.Runtime.CompilerServices.CallerLineNumberAttribute
(§22.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute
(§22.5.6.3) ySystem.Runtime.CompilerServices.CallerMemberNameAttribute
(§22.5.6.4), que se usan para proporcionar información sobre el contexto de llamada a parámetros opcionales.
Los atributos de análisis estático que aceptan valores NULL (§22.5.7) pueden mejorar la corrección de las advertencias generadas para valores NULL y estados NULL (§8.9.5).
Un entorno de ejecución puede proporcionar atributos definidos por la implementación adicionales que afectan a la ejecución de un programa de C#.
22.5.2 El atributo AttributeUsage
El atributo AttributeUsage
se usa para describir la manera en que se puede usar la clase de atributo.
Una clase que esté decorada con el AttributeUsage
atributo se derivará de System.Attribute
, directa o indirectamente. De lo contrario, se produce un error en tiempo de compilación.
Nota: Para obtener un ejemplo de uso de este atributo, consulte §22.2.2. nota final
22.5.3 El atributo condicional
22.5.3.1 General
El atributo Conditional
habilita la definición de métodos condicionales y clases de atributos condicionales.
22.5.3.2 Métodos condicionales
Un método decorado con el Conditional
atributo es un método condicional. Por lo tanto, cada método condicional está asociado a los símbolos de compilación condicional declarados en sus Conditional
atributos.
Ejemplo:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }
declara
Eg.M
como un método condicional asociado a los dos símbolosALPHA
de compilación condicional yBETA
.ejemplo final
Se incluye una llamada a un método condicional si se define uno o varios de sus símbolos de compilación condicional asociados en el punto de llamada; de lo contrario, se omite la llamada.
Un método condicional está sujeto a las restricciones siguientes:
- El método condicional debe ser un método en un class_declaration o struct_declaration. Se produce un error en tiempo de compilación si el
Conditional
atributo se especifica en un método en una declaración de interfaz. - El método condicional tendrá un tipo de valor devuelto de
void
. - El método condicional no se marcará con el
override
modificador . Sin embargo, un método condicional se puede marcar con elvirtual
modificador . Las invalidaciones de este método son condicionales implícitamente y no se marcarán explícitamente con unConditional
atributo . - El método condicional no será una implementación de un método de interfaz. De lo contrario, se produce un error en tiempo de compilación.
- Los parámetros del método condicional no serán parámetros de salida.
Además, se produce un error en tiempo de compilación si se crea un delegado a partir de un método condicional.
Ejemplo: El ejemplo
#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(); } }
declara
Class1.M
como un método condicional.Class2
El método llamaTest
a este método. Dado que se define el símboloDEBUG
de compilación condicional, siClass2.Test
se llama a , llamará aM
. Si no se hubiera definido el símboloDEBUG
,Class2.Test
no llamaría aClass1.M
.ejemplo final
Es importante comprender que la inclusión o exclusión de una llamada a un método condicional se controla mediante los símbolos de compilación condicional en el punto de la llamada.
Ejemplo: en el código siguiente
// 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 } }
las clases
Class2
yClass3
cada una contienen llamadas al métodoClass1.F
condicional , que es condicional en función de si se define o noDEBUG
. Dado que este símbolo se define en el contexto deClass2
, pero noClass3
, se incluye la llamada aF
enClass2
, mientras que la llamada aF
enClass3
se omite.ejemplo final
El uso de métodos condicionales en una cadena de herencia puede resultar confuso. Las llamadas realizadas a un método condicional a través base
de , del formulario base.M
, están sujetas a las reglas normales de llamada al método condicional.
Ejemplo: en el código siguiente
// 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
incluye una llamada a laM
definida en su clase base. Esta llamada se omite porque el método base está condicional en función de la presencia del símboloDEBUG
, que no está definido. Por lo tanto, el método escribe solo en la consola "Class2.M executed
". El uso prudente de pp_declarations puede eliminar estos problemas.ejemplo final
22.5.3.3 Clases de atributos condicionales
Una clase de atributo (§22.2) decorada con uno o varios Conditional
atributos es una clase de atributo condicional. Por lo tanto, una clase de atributo condicional está asociada a los símbolos de compilación condicional declarados en sus Conditional
atributos.
Ejemplo:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttribute
declara como una clase de atributo condicional asociada a los símbolos de compilaciones condicionalesALPHA
yBETA
.ejemplo final
Las especificaciones de atributo (§22.3) de un atributo condicional se incluyen si se define uno o varios de sus símbolos de compilación condicional asociados en el punto de especificación; de lo contrario, se omite la especificación del atributo.
Es importante tener en cuenta que la inclusión o exclusión de una especificación de atributo de una clase de atributo condicional se controla mediante los símbolos de compilación condicional en el punto de la especificación.
Ejemplo: en el ejemplo
// 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 {}
las clases y están decoradas
Class1
con el atributoTest
, que es condicional en función de si se define o noDEBUG
.Class2
Dado que este símbolo se define en el contexto deClass1
, pero noClass2
, se incluye la especificación del atributo Test enClass1
, mientras que se omite la especificación delTest
atributo enClass2
.ejemplo final
22.5.4 El atributo Obsoleto
El atributo Obsolete
se usa para marcar tipos y miembros de tipos que ya no se deben usar.
Si un programa usa un tipo o miembro que está decorado con el Obsolete
atributo , el compilador emitirá una advertencia o un error. En concreto, el compilador emitirá una advertencia si no se proporciona ningún parámetro de error o si se proporciona el parámetro de error y tiene el valor false
. El compilador emitirá un error si se especifica el parámetro error y tiene el valor true
.
Ejemplo: en el código siguiente
[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(); } }
la clase
A
está decorada con elObsolete
atributo . Cada uso deA
enMain
da como resultado una advertencia que incluye el mensaje especificado, "Esta clase está obsoleta; use la claseB
en su lugar".ejemplo final
22.5.5 El atributo AsyncMethodBuilder
Este atributo se describe en §15.15.1.
22.5.6 Atributos de información de llamada
22.5.6.1 General
Para fines como el registro y los informes, a veces resulta útil que un miembro de función obtenga cierta información en tiempo de compilación sobre el código de llamada. Los atributos de información del autor de la llamada proporcionan una manera de pasar dicha información de forma transparente.
Cuando se anota un parámetro opcional con uno de los atributos de información del autor de la llamada, omitir el argumento correspondiente en una llamada no necesariamente hace que se sustituya el valor del parámetro predeterminado. En su lugar, si la información especificada sobre el contexto de llamada está disponible, esa información se pasará como el valor del argumento.
Ejemplo:
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); }
Una llamada a
Log()
sin argumentos imprimiría el número de línea y la ruta de acceso del archivo de la llamada, así como el nombre del miembro en el que se produjo la llamada.ejemplo final
Los atributos de información del autor de la llamada pueden producirse en parámetros opcionales en cualquier lugar, incluido en declaraciones de delegados. Sin embargo, los atributos específicos de la información de llamada tienen restricciones en los tipos de los parámetros que pueden atribuir, de modo que siempre habrá una conversión implícita de un valor sustituido al tipo de parámetro.
Es un error tener el mismo atributo de información del autor de la llamada en un parámetro de la definición e implementación de parte de una declaración de método parcial. Solo se aplican atributos de información del autor de la llamada en el elemento de definición, mientras que los atributos de información del autor de la llamada que se producen solo en la parte de implementación se omiten.
La información del autor de la llamada no afecta a la resolución de sobrecargas. Dado que los parámetros opcionales con atributos todavía se omiten en el código fuente del autor de la llamada, la resolución de sobrecarga omite esos parámetros de la misma manera que omite otros parámetros opcionales omitidos (§12.6.4).
La información del autor de la llamada solo se sustituye cuando se invoca explícitamente una función en el código fuente. Las invocaciones implícitas, como las llamadas al constructor primario implícitas, no tienen una ubicación de origen y no sustituyen la información del autor de la llamada. Además, las llamadas enlazadas dinámicamente no sustituyen la información del autor de la llamada. Cuando se omite un parámetro con atributos de información de llamada en tales casos, se usa en su lugar el valor predeterminado especificado del parámetro.
Una excepción es expresiones de consulta. Se consideran expansiones sintácticas y, si las llamadas que se expanden para omitir parámetros opcionales con atributos de información de llamada, se sustituirá la información del autor de la llamada. La ubicación utilizada es la ubicación de la cláusula de consulta a la que se generó la llamada.
Si se especifica más de un atributo de información de llamada en un parámetro determinado, se reconocen en el orden siguiente: CallerLineNumber
, , CallerMemberName
CallerFilePath
. Tenga en cuenta la siguiente declaración de parámetros:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber
tiene prioridad y se omiten los otros dos atributos. Si CallerLineNumber
se omite, CallerFilePath
tendría prioridad y CallerMemberName
se omitiría. El orden léxico de estos atributos es irrelevante.
22.5.6.2 El atributo CallerLineNumber
El atributo System.Runtime.CompilerServices.CallerLineNumberAttribute
se permite en parámetros opcionales cuando hay una conversión implícita estándar (§10.4.2) del valor int.MaxValue
constante al tipo del parámetro. Esto garantiza que cualquier número de línea no negativo hasta ese valor se pueda pasar sin errores.
Si una invocación de función de una ubicación en el código fuente omite un parámetro opcional con CallerLineNumberAttribute
, se usa un literal numérico que representa el número de línea de esa ubicación como argumento para la invocación en lugar del valor de parámetro predeterminado.
Si la invocación abarca varias líneas, la línea elegida depende de la implementación.
El número de línea puede verse afectado por #line
las directivas (§6.5.8).
22.5.6.3 El atributo CallerFilePath
El atributo System.Runtime.CompilerServices.CallerFilePathAttribute
se permite en parámetros opcionales cuando hay una conversión implícita estándar (§10.4.2) desde string
hasta el tipo del parámetro.
Si una invocación de función de una ubicación en el código fuente omite un parámetro opcional con CallerFilePathAttribute
, se usa un literal de cadena que representa la ruta de acceso del archivo de esa ubicación como argumento para la invocación en lugar del valor de parámetro predeterminado.
El formato de la ruta de acceso del archivo depende de la implementación.
La ruta de acceso del archivo puede verse afectada por #line
las directivas (§6.5.8).
22.5.6.4 El atributo CallerMemberName
El atributo System.Runtime.CompilerServices.CallerMemberNameAttribute
se permite en parámetros opcionales cuando hay una conversión implícita estándar (§10.4.2) desde string
hasta el tipo del parámetro.
Si una invocación de función desde una ubicación dentro del cuerpo de un miembro de función o dentro de un atributo aplicado al propio miembro de función o a su tipo devuelto, parámetros o parámetros de tipo en el código fuente omite un parámetro opcional con CallerMemberNameAttribute
, entonces un literal de cadena que representa el nombre de ese miembro se usa como argumento para la invocación en lugar del valor de parámetro predeterminado.
En el caso de las invocaciones que se producen dentro de métodos genéricos, solo se usa el propio nombre del método, sin la lista de parámetros de tipo.
En el caso de las invocaciones que se producen dentro de implementaciones explícitas del miembro de interfaz, solo se usa el propio nombre del método, sin la calificación de interfaz anterior.
En el caso de las invocaciones que se producen dentro de los descriptores de acceso de propiedad o evento, el nombre de miembro utilizado es el de la propia propiedad o evento.
En el caso de las invocaciones que se producen en los descriptores de acceso del indexador, el nombre de miembro usado es proporcionado por un IndexerNameAttribute
elemento (§22.6) en el miembro del indexador, si está presente o el nombre Item
predeterminado de lo contrario.
En el caso de las invocaciones que se producen dentro de los inicializadores de campo o evento, el nombre del miembro usado es el nombre del campo o evento que se va a inicializar.
Para las invocaciones que se producen en declaraciones de constructores de instancia, constructores estáticos, finalizadores y operadores, el nombre de miembro usado depende de la implementación.
22.5.7 Atributos de análisis de código
22.5.7.1 General
Los atributos de esta sección se usan para proporcionar información adicional para admitir un compilador que proporciona diagnósticos de nulabilidad y estado NULL (§8.9.5). Un compilador no es necesario para realizar ningún diagnóstico de estado NULL. La presencia o ausencia de estos atributos no afecta al lenguaje ni al comportamiento de un programa. Un compilador que no proporciona diagnósticos de estado NULL leerá y omitirá la presencia de estos atributos. Un compilador que proporcione diagnósticos de estado NULO usará el significado definido en esta sección para cualquiera de estos atributos que usa para informar a sus diagnósticos.
Los atributos de análisis de código se declaran en el espacio de nombres System.Diagnostics.CodeAnalysis
.
Atributo | Significado |
---|---|
AllowNull (§22.5.7.2) |
Un argumento que no acepta valores NULL puede ser NULL. |
DisallowNull (§22.5.7.3) |
Un argumento que admite un valor NULL nunca debe ser NULL. |
MaybeNull (§22.5.7.6) |
un valor devuelto que no acepta valores NULL puede ser NULL. |
NotNull (§22.5.7.8) |
un valor devuelto que admite un valor NULL nunca será NULL. |
MaybeNullWhen (§22.5.7.7) |
Un argumento que no acepta valores NULL puede ser NULL cuando el método devuelve el valor bool especificado. |
NotNullWhen (§22.5.7.10) |
Un argumento que acepta valores NULL no será NULL cuando el método devuelva el valor especificado bool . |
NotNullIfNotNull (§22.5.7.9) |
Un valor devuelto no es NULL si el argumento del parámetro especificado no es NULL. |
DoesNotReturn (§22.5.7.4) |
Este método nunca devuelve. |
DoesNotReturnIf (§22.5.7.5) |
este método nunca devuelve un valor si el parámetro bool asociado tiene el valor especificado. |
Las secciones siguientes de §22.5.7.1 son normativas condicionalmente.
22.5.7.2 El atributo AllowNull
Especifica que se permite un valor NULL como entrada aunque el tipo correspondiente no lo permita.
Ejemplo: considere la siguiente propiedad de lectura y escritura que nunca devuelve
null
porque tiene un valor predeterminado razonable. Sin embargo, un usuario puede proporcionar null al descriptor de acceso set para establecer la propiedad en ese valor predeterminado.#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }
Dado el siguiente uso del descriptor de acceso set de esa propiedad
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNull
sin el atributo , el compilador puede generar una advertencia porque la propiedad con tipo que no acepta valores NULL parece establecerse en un valor NULL. La presencia del atributo suprime esa advertencia. ejemplo final
22.5.7.3 El atributo DisallowNull
Especifica que no se permite un valor NULL como entrada aunque el tipo correspondiente lo permita.
Ejemplo: Considere la siguiente propiedad en la que null es el valor predeterminado, pero los clientes solo pueden establecerla en un valor distinto de NULL.
#nullable enable public class X { [DisallowNull] public string? ReviewComment { get => _comment; set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null"); } private string? _comment = default; }
El descriptor de acceso get podría devolver el valor predeterminado de
null
, por lo que el compilador puede advertir de que debe comprobarse antes del acceso. Además, advierte a los autores de llamadas que, aunque podría ser NULL, los autores de llamadas no deben establecerlo explícitamente en NULL. ejemplo final
22.5.7.4 El atributo DoesNotReturn
Especifica que un método determinado nunca devuelve.
Ejemplo: Tenga en cuenta lo siguiente:
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; }
La presencia del atributo ayuda al compilador de varias maneras. En primer lugar, el compilador puede emitir una advertencia si hay una ruta de acceso en la que el método puede salir sin producir una excepción. En segundo lugar, el compilador puede suprimir advertencias que aceptan valores NULL en cualquier código después de una llamada a ese método, hasta que se encuentre una cláusula catch adecuada. En tercer lugar, el código inaccesible no afectará a ningún estado NULL.
El atributo no cambia la accesibilidad (§13.2) ni el análisis de asignación definitiva (§9.4) en función de la presencia de este atributo. Solo se usa para afectar a las advertencias de nulabilidad. ejemplo final
22.5.7.5 El atributo DoesNotReturnIf
Especifica que un método determinado nunca devuelve si el parámetro asociado bool
tiene el valor especificado.
Ejemplo: Tenga en cuenta lo siguiente:
#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!; }
ejemplo final
22.5.7.6 El atributo MaybeNull
Especifica que un valor devuelto que no acepta valores NULL puede ser NULL.
Ejemplo: Considere el siguiente método genérico:
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
La idea de este código es que si
T
se reemplaza porstring
,T?
se convierte en una anotación que acepta valores NULL. Sin embargo, este código no es legal porqueT
no está restringido para ser un tipo de referencia. Sin embargo, agregar este atributo resuelve el problema:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
El atributo informa a los llamadores de que el contrato implica un tipo que no acepta valores NULL, pero el valor devuelto puede ser
null
realmente . ejemplo final
22.5.7.7 El atributo MaybeNullWhen
Especifica que un argumento que no acepta valores NULL puede ser null
cuando el método devuelve el valor especificado bool
. Esto es similar al MaybeNull
atributo (§22.5.7.6), pero incluye un parámetro para el valor devuelto especificado.
22.5.7.8 El atributo NotNull
Especifica que un valor que acepta valores NULL nunca será null
si el método devuelve (en lugar de iniciar).
Ejemplo: Tenga en cuenta lo siguiente:
#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); }
Cuando se habilitan los tipos de referencia null, el método
ThrowWhenNull
se compila sin advertencias. Cuando devuelve ese método, se garantiza que elvalue
argumento nonull
sea . Sin embargo, es aceptable llamarThrowWhenNull
a con una referencia nula. ejemplo final
22.5.7.9 El atributo NotNullIfNotnull
Especifica que un valor devuelto no null
es si el argumento del parámetro especificado no null
es .
Ejemplo: el estado NULL de un valor devuelto podría depender del estado NULL de uno o varios argumentos. Para ayudar al análisis del compilador cuando un método siempre devuelve un valor distinto de NULL cuando determinados argumentos no
null
son elNotNullIfNotNull
atributo se puede usar. Observe el método siguiente:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
Si el
url
argumento nonull
es ,null
no se devuelve. Cuando se habilitan las referencias que aceptan valores NULL, esa firma funciona correctamente, siempre que la API nunca acepte un argumento NULL. Sin embargo, si el argumento podría ser NULL, el valor devuelto también podría ser NULL. Para expresar ese contrato correctamente, anote este método de la siguiente manera:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }
ejemplo final
22.5.7.10 El atributo NotNullWhen
Especifica que un argumento que acepta valores NULL no será null
cuando el método devuelva el valor especificado bool
.
Ejemplo: el método
String.IsNullOrEmpty(String)
de biblioteca devuelvetrue
cuando el argumento esnull
o una cadena vacía. Se trata de una forma de comprobación nula: los llamadores no necesitan comprobar null-check el argumento si el método devuelvefalse
. Para que un método como este que admita valores NULL, haga que el parámetro escriba un tipo de referencia que acepta valores NULL y agregue el atributo NotNullWhen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
ejemplo final
22.6 Atributos para la interoperación
Para la interoperación con otros lenguajes, se puede implementar un indexador mediante propiedades indexadas. Si no hay ningún IndexerName
atributo presente para un indexador, el nombre Item
se usa de forma predeterminada. El IndexerName
atributo permite que un desarrollador invalide este valor predeterminado y especifique un nombre diferente.
Ejemplo: De forma predeterminada, el nombre de un indexador es
Item
. Esto se puede invalidar, como se indica a continuación:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
Ahora, el nombre del indexador es
TheItem
.ejemplo final
ECMA C# draft specification