Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
23.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 decorándolo con 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 asociar atributos a diversas entidades de programa y recuperar la información de los atributos en un entorno en tiempo de ejecución.
Nota: Por ejemplo, un marco podría definir un atributo
HelpAttributeque se puede colocar en determinados elementos del 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 (§23.2), que pueden tener parámetros posicionales y con nombre (§23.2.3). Los atributos se adjuntan a entidades de un programa de C# mediante especificaciones de atributo (§23.3) y se pueden recuperar en tiempo de ejecución como instancias de atributo (§23.4).
23.2 Clases de atributos
23.2.1 General
Una clase que se deriva de la clase abstracta System.Attribute, 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, los nombres de las clases de atributo llevan el sufijo Attribute. En el uso de un atributo se puede incluir u omitir este sufijo.
Una declaración de clase genérica no usa System.Attribute como una clase base directa o indirecta.
Ejemplo:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attributeejemplo final
23.2.2 Uso de atributos
El atributo AttributeUsage (§23.5.2) se usa para describir cómo se puede usar una clase de atributo.
AttributeUsage tiene un parámetro posicional (§23.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
SimpleAttributeque se puede colocar solo en class_declaration e interface_declaration, y muestra varios usos del atributoSimple.[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, se puede omitir el sufijoAttribute, lo que da como resultado el nombre cortoSimple. 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 (§23.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
AuthorAttributey se muestra una declaración de clase con dos usos del atributoAuthor:[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 (§23.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 de atributo X que no tiene un atributo AttributeUsage asociado, como en
class X : Attribute { ... }
es equivalente a lo siguiente:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
23.2.3 Parámetros posicionales y con nombre
Las clases de atributo pueden tener parámetros posicionales y parámetros con 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 debe tener un descriptor de acceso get público y un descriptor de acceso set público.
Ejemplo: en el ejemplo siguiente se define una clase de atributo denominada
HelpAttributeque tiene un parámetro posicional,url, y un parámetro con nombre,Topic. Aunque es no estática y pública, la propiedadUrlno 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
23.2.4 Tipos de parámetros de atributo
Los tipos de parámetros posicionales y con nombre para una clase de atributo se limitan al tipo de parámetro de atributos, que son:
- Uno de los siguientes tipos:
bool,byte,char,double,float,int,long,sbyte,short,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 debe usarse como parámetro posicional o con nombre en una especificación de atributos.
Especificación de atributo 23.3
La aplicación de un atributo definido previamente a una entidad de programa se denomina especificación de atributo. 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_declarations (§14.7), class_member_declarations (§15.3), interface_member_declarations (§19.7). 4), struct_member_declarations (§16.3), enum_member_declarations (§20.2), accessor_declarations (§15.7.3), event_accessor_declarations (§15.8)), elementos de parameter_lists (§15.6.2) y elementos de type_parameter_lists (§15.2.3).
Los atributos se especifican en la sección de atributoss. Una sección de atributo consta de un par de corchetes, entre los que se incluye 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 están organizadas las secciones adjuntas a la misma entidad de programa, no es importante. Por ejemplo, las especificaciones de atributos [A][B], [B][A], [A, B] y [B, A] son equivalentes.
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
;
Para el global_attribute_target de producción y, en el texto siguiente, identifier se escribirá igual que assembly o module, y esa igualdad se define en §6.4.3. Para el attribute_target, y en el texto siguiente, identifier debe escribirse no una forma que no sea igual a assembly o module, de acuerdo con la misma definición de igualdad anterior.
Un atributo consta de un 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 una attribute_argument_expression; un argumento con nombre consta de un nombre, seguido de un signo igual, seguido de una attribute_argument_expression, que, juntos, están limitados por las mismas reglas que la asignación simple. El orden de los argumentos con nombre no es importante.
Nota: Para mayor comodidad, se permite usar una coma final en una 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 necesita un global_attribute_target_specifier. Cuando el global_attribute_target es igual a:
-
assembly: el destino es el ensamblado que lo contiene -
module: el destino es el módulo que lo contiene
No se permiten otros valores para global_attribute_target.
Los nombres del attribute_target estandarizado 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, sin descriptores de acceso) también puede tener un atributo con este destino. -
param: un descriptor de acceso de definición de propiedades, un descriptor de acceso de definición de indizadores, un descriptor de acceso para agregar y quitar eventos, y un parámetro en un constructor, método y operador. -
property: una propiedad y un indexador. -
return: un delegado, método, operador, descriptor de acceso get de propiedad y descriptor de acceso get de indexador. -
type: un delegado, clase, estructura, enumeración e interfaz. -
typevar: un parámetro de tipo.
Algunos contextos permiten especificar 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 anular 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 de devolución.
-
- 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 de devolución.
-
- 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 de devolución.
-
- 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 de devolución.
-
- Para un atributo especificado en un 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. -
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 eventos. 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 eventos 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 u 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, cuyos propósitos se definen en la implementación. Una implementación que no reconozca dicho attribute_target emitirá una advertencia y omitirá la attribute_section que la contiene.
Por convención, los nombres de las clases de atributo llevan el sufijo Attribute. Un attribute_name puede incluir u omitir este sufijo. En concreto, se resuelve un attribute_name de la siguiente manera:
- Si el identificador más a la derecha 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. - De lo contrario,
- El attribute_name se resuelve como un type_name (§7.8), excepto por el hecho de 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
Attributese anexan al identificador más a la derecha del attribute_name y la cadena resultante de tokens se resuelve como un type_name (§7.8), excepto por el hecho de 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 por el hecho de 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 del attribute_name. De lo contrario, se produce un error 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 a la derecha es un identificador textual (§6.4.3), solo se encuentra una coincidencia 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
ExampleyExampleAttribute. El atributo[Example]es ambiguo, ya que podría hacer referencia aExampleo aExampleAttribute. El uso de un identificador textual permite especificar la intención exacta en esos casos tan 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 produce un error en tiempo de compilación si se usa 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 una attribute_argument_expression si se cumplen todas las instrucciones siguientes:
- El tipo de es un tipo de
Eparámetro de atributo (§23.2.4). - En tiempo de compilación, el valor de
Ese 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.
23.4 Instancias de atributo
23.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.
23.4.2 Compilación de un atributo
La compilación de un atributo con la clase de atributo T, positional_argument_listP, named_argument_listN, y especificado en una entidad de programa E se compila en un ensamblado A por medio de los siguientes pasos:
- 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 constructor de instanciaCenTque se puede invocar en tiempo de ejecución. - Si
Cno tiene accesibilidad pública, se produce un error en tiempo de compilación. - Para cada named_argument
ArgenN:- Deje que
Namesea el identificador del named_argumentArg. -
Nameidentificará una propiedad o campo público de lectura y escritura no estático enT. SiTno tiene este campo o propiedad, se produce un error en tiempo de compilación.
- Deje que
- Si alguno de los valores de positional_argument_list
Po uno de los valores de named_argument_listNes de tipoSystem.Stringy 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 (§23.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 el 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 del compilador como resultado de compilar el programa que contiene el atributo: la clase de atributo
T, el constructor de instanciaCenT, la positional_argument_listP, la named_argument_listNy la entidad de programa asociadaE, con los valores resueltos por completo en tiempo de compilación.
23.4.3 Recuperación en tiempo de ejecución de una instancia de atributo
Con los términos definidos en §23.4.2, la instancia de atributo representada por T, C, Py N, y asociada E a 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 constructor de instanciasCy los valores que se determinan en tiempo de compilación. Estos pasos dan como resultado una excepción o generan una instanciaOdeT. - Para cada named_argument
ArgenN, en orden:- Deje que
Namesea el identificador del named_argumentArg. SiNameno identifica un campo o propiedad de lectura y escritura públicos no estáticos enO, se produce una excepción. - Deje que
Valuesea el resultado de evaluar la attribute_argument_expression deArg. - Si
Nameidentifica un campo enO, establezca este campo enValue. - De lo contrario, Name identifica una propiedad en
O. Defina esta propiedad en Value. - El resultado es
O, una instancia de la clase de atributoTque se ha inicializado con la positional_argument_listPy la named_argument_listN.
- Deje que
Nota: El formato para almacenar
T,C,P,N(y asociarlo aE) enAy el mecanismo para especificarEy recuperarT,C,P,NdeA(y, por tanto, cómo se obtiene una instancia de atributo en tiempo de ejecución) está fuera del ámbito de esta especificación. nota final
23.5 Atributos reservados
23.5.1 General
Varios atributos afectan al lenguaje de alguna manera. Estos atributos son los siguientes:
-
System.AttributeUsageAttribute(§23.5.2), que se usa para describir las formas en que se puede usar una clase de atributo. -
System.Diagnostics.ConditionalAttribute(§23.5.3), es una clase de atributo de uso múltiple 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(§23.5.4), que se usa para marcar un miembro como obsoleto. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5), que se usa para establecer un generador de tareas para un método asincrónico. -
System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3) ySystem.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4), que se usan para proporcionar información sobre el contexto de llamada a parámetros opcionales. -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8), que se usa para especificar el parámetro para el token de cancelación en un iterador asincrónico.
Los atributos de análisis estático que aceptan valores NULL (§23.5.7) pueden mejorar la exactitud de las advertencias generadas para las nulas y los 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#.
23.5.2 El atributo AttributeUsage
El atributo AttributeUsage se usa para describir la manera en que se puede utilizar la clase de atributo.
Una clase que esté decorada con el atributo AttributeUsage se derivará de System.Attribute, directa o indirectamente. De lo contrario, se produce un error de compilación.
Nota: Para obtener un ejemplo del uso de este atributo, consulte §23.2.2. nota final
23.5.3 El atributo condicional
23.5.3.1 General
El atributo Conditional habilita la definición de los métodos condicionalesy las clases de atributo condicional.
23.5.3.2 Métodos condicionales
Un método decorado con el atributo Conditional 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 atributos Conditional.
Ejemplo:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }declara
Eg.Mcomo un método condicional asociado a los dos símbolos de compilación condicionalALPHAyBETA.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 una class_declaration o una struct_declaration. Se produce un error en tiempo de compilación si el atributo
Conditionalse especifica en un método en una declaración de interfaz. - El método condicional no debe ser un descriptor de acceso de una propiedad, indexador o evento.
- El método condicional tendrá un tipo de valor de devolución de
void. - El método condicional no se marcará con el modificador
override. Sin embargo, un método condicional se puede marcar con el modificadorvirtual. Las anulaciones de este método son condicionales implícitamente y no se marcarán explícitamente con un atributoConditional. - El método condicional no será una implementación de un método de interfaz. De lo contrario, se produce un error de compilación.
- Los parámetros del método condicional no serán parámetros de salida.
Nota: Los atributos con (
AttributeUsage§23.2.2), incluidosAttributeTargets.Method, normalmente, se pueden aplicar a los descriptores de acceso de propiedades, indizadores y eventos. Las restricciones anteriores prohíben este uso delConditionalatributo. nota final
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.Mcomo un método condicional. El métodoClass2deTestllama a este método. Dado que se define el símbolo de compilación condicionalDEBUG, si se llama aClass2.Test, llamará aM. Si no se hubiera definido el símboloDEBUG,Class2.Testno 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.
Example: En el código de ejemplo 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
Class2yClass3contienen, cada una, llamadas al método condicionalClass1.F, 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 aFenClass2, mientras que la llamada aFenClass3se omite.ejemplo final
El uso de métodos condicionales en una cadena de herencia puede resultar confuso. Las llamadas que se realizan a un método condicional a través de base, del formulario base.M, están sujetas a las reglas normales de llamada al método condicional.
Example: En el código de ejemplo 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 } }
Class2incluye una llamada a laMdefinida en su clase base. Esta llamada se omite porque el método base está condicionado a 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
23.5.3.3 Clases de atributos condicionales
Una clase de atributo (§23.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 atributos Conditional.
Ejemplo:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}declara
TestAttributecomo una clase de atributo condicional asociada a los símbolos de compilaciones condicionalesALPHAyBETA.ejemplo final
Las especificaciones de atributo (§23.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
Class1yClass2están decoradas con el atributoTest, que está condicionado a si se define o noDEBUG. 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 del atributoTestenClass2.ejemplo final
23.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á representado con el atributo Obsolete, un compilador emitirá una advertencia o un error. En concreto, un 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. Un compilador emitirá un error si se especifica el parámetro error y tiene el valor true.
Example: En el código de ejemplo 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
Aestá decorada con el atributoObsolete. Cada uso deAenMainda como resultado una advertencia que incluye el mensaje especificado, "Esta clase está obsoleta; use la claseBen su lugar".ejemplo final
23.5.5 El atributo AsyncMethodBuilder
Este atributo se describe en §15.14.1.
23.5.6 Atributos de información del llamador
23.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 son 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, incluso en declaraciones de delegados. Sin embargo, los atributos específicos de información del autor de la llamada tienen restricciones en lo que se refiere a los tipos de los parámetros a los 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 tanto de la parte definidora como de la parte implementadora 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 las sobrecargas. Dado que los parámetros opcionales con atributos se siguen omitiendo en el código fuente del autor de la llamada, la resolución de la 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 a la información del autor de la llamada. Además, las llamadas enlazadas dinámicamente no sustituyen a la información del autor de la llamada. Cuando se omite un parámetro con atributos de información del autor de la llamada en estos casos, se usa en su lugar el valor predeterminado del parámetro especificado.
Una excepción son las expresiones de consulta. Se consideran expansiones sintácticas y, si las llamadas se expanden para omitir parámetros opcionales con atributos de información del autor de la 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 desde la que se generó la llamada.
Si se especifica más de un atributo de información del autor de la llamada en un parámetro determinado, se reconocen en el orden siguiente: CallerLineNumber, CallerFilePath, CallerMemberName. Observe 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.
23.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 de constante int.MaxValue 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 desde una ubicación en el código fuente omite un parámetro opcional con CallerLineNumberAttribute, como argumento para la invocación se usa un literal numérico que representa el número de línea de esa ubicació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 las directivas #line (§6.5.8).
23.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) string al tipo del parámetro.
Si una invocación de función desde una ubicación en el código fuente omite un parámetro opcional con CallerFilePathAttribute, como argumento para la invocación se usa un literal de cadena que representa la ruta de acceso al archivo de esa ubicació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 las directivas #line (§6.5.8).
23.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) string al 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 de devolución, parámetros o parámetros de tipo en el código fuente omite un parámetro opcional con CallerMemberNameAttribute, como argumento para la invocación, se usa un literal de cadena que representa el nombre de ese miembro 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 el proporcionado por un IndexerNameAttribute elemento (§23.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.
23.5.7 Atributos de análisis de código
23.5.7.1 General
Los atributos de esta subcláusula se usan para proporcionar información adicional para respaldar un compilador que proporciona diagnósticos de nulabilidad y estado nulo (§8.9.5). No es necesario usar un compilador para realizar un diagnóstico del 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 subclausa 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 (§23.5.7.2) |
Un argumento que no acepta valores NULL puede ser NULL. |
DisallowNull (§23.5.7.3) |
Un argumento que admite un valor NULL nunca debe ser NULL. |
MaybeNull (§23.5.7.6) |
un valor devuelto que no acepta valores NULL puede ser NULL. |
NotNull (§23.5.7.8) |
un valor devuelto que admite un valor NULL nunca será NULL. |
MaybeNullWhen (§23.5.7.7) |
Un argumento que no acepta valores NULL puede ser NULL cuando el método devuelve el valor bool especificado. |
NotNullWhen (§23.5.7.10) |
Un argumento que admite un valor NULL nunca será NULL cuando el método devuelva el valor bool especificado. |
NotNullIfNotNull (§23.5.7.9) |
Un valor devuelto no es NULL si el argumento del parámetro especificado no es NULL. |
DoesNotReturn (§23.5.7.4) |
Este método nunca vuelve. |
DoesNotReturnIf (§23.5.7.5) |
este método nunca devuelve un valor si el parámetro bool asociado tiene el valor especificado. |
Las siguientes subclases de §23.5.7.1 son normativas condicionalmente.
23.5.7.2 El atributo AllowNull
Especifica que se permite un valor NULL como entrada, incluso si el tipo correspondiente no lo permite.
Ejemplo: considere la siguiente propiedad de lectura y escritura que nunca devuelve
nullporque 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 AllowNullsin el atributo, un compilador puede generar una advertencia porque la propiedad con tipo que no acepta valores null parece estar asignada a un valor null. La presencia del atributo suprime esa advertencia. ejemplo final
23.5.7.3 El atributo DisallowNull
Especifica que no se permite un valor NULL como entrada, incluso si el tipo correspondiente lo permite.
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 un compilador puede advertir de que debe comprobarse antes del acceso. Además, advierte a los autores de la llamada que, aunque podría ser NULL, no deberían establecerlo de forma explícita en NULL. ejemplo final
23.5.7.4 El atributo DoesNotReturn
Especifica que un método determinado nunca devuelve nada.
Ejemplo: Considere 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 a un compilador de varias maneras. En primer lugar, un compilador puede emitir una advertencia si hay una ruta de acceso por la que el método puede salir sin iniciar una excepción. En segundo lugar, un compilador puede suprimir advertencias de anulabilidad en cualquier código después de una llamada a ese método, hasta que se encuentre una cláusula catch adecuada. Por último, 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
23.5.7.5 El atributo DoesNotReturnIf
Especifica que un método determinado nunca devuelve nada si el parámetro asociado bool tiene el valor especificado.
Ejemplo: Considere 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
23.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
Tse reemplaza porstring,T?se convierte en una anotación que acepta valores NULL. Sin embargo, este código no es válido, porqueTno está restringido para ser un tipo de referencia. Sin embargo, la adición de 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 autores de la llamada de que el contrato implica un tipo que no acepta valores NULL, pero el valor de devolución puede realmente ser
null. ejemplo final
23.5.7.7 El atributo MaybeNullWhen
Especifique que un argumento de entrada que no acepta valores NULL puede ser null cuando el método devuelve el valor bool especificado. Esto es similar al MaybeNull atributo (§23.5.7.6), pero incluye un parámetro para el valor devuelto especificado.
23.5.7.8 El atributo NotNull
Especifica que un valor que acepta valores NULL nunca será null si el método devuelve algo (en lugar de generar algo).
Ejemplo: Considere 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 que aceptan valores NULL, el método
ThrowWhenNullse compila sin advertencias. Cuando ese método devuelve un valor, se garantiza que el argumentovalueno esnull. Pero es aceptable llamar aThrowWhenNullcon una referencia NULL. ejemplo final
23.5.7.9 El atributo NotNullIfNotnull
Especifica que un valor de devolución no es null si el argumento del parámetro especificado no es null.
Example: el estado NULL de un valor de devolución podría depender del estado NULL de uno o más argumentos. Para ayudar al análisis de un compilador cuando un método siempre devuelve un valor no nulo cuando ciertos argumentos no son
null, se puede utilizar el atributoNotNullIfNotNull. Observe el método siguiente:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }Si el argumento
urlno esnull, no se devuelvenull. Una vez que se hayan habilitado las referencias que admiten NULL, esa firma funcionará correctamente, siempre que la API no acepte nunca un argumento NULL. Pero si el argumento puede ser NULL, el valor de devolución también podría serlo. Para expresar ese contrato correctamente, anote este método de la siguiente manera:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }ejemplo final
23.5.7.10 El atributo NotNullWhen
Especifique que un argumento de entrada que no acepta valores NULL no sea null cuando el método devuelve el valor bool especificado.
Ejemplo: el método de biblioteca
String.IsNullOrEmpty(String)devuelvetruecuando el argumento esnullo una cadena vacía. Es una forma de comprobación de valores NULL: No es necesario que los autores de la llamada comprueben los valores NULL del argumento si el método devuelvefalse. Para que un método como este admita valores NULL, haga que el tipo de parámetro sea un tipo de referencia que admita valores NULL y añada el atributo NotNullWhen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }ejemplo final
23.5.8 El atributo EnumeratorCancellation
Especifica el parámetro que representa para CancellationToken un iterador asincrónico (§15.15). El argumento de este parámetro se combinará con el argumento pasado a IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken). Este token combinado se sondea mediante IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2). Los tokens se combinarán en un único token como si fuera por CancellationToken.CreateLinkedTokenSource y su Token propiedad. El token combinado se cancelará si se cancela cualquiera de los dos tokens de origen. El token combinado se ve como argumento para el método de iterador asincrónico (§15.15) en el cuerpo de ese método.
Se trata de un error si el System.Runtime.CompilerServices.EnumeratorCancellation atributo se aplica a más de un parámetro. El compilador puede generar una advertencia si:
- El
EnumeratorCancellationatributo se aplica a un parámetro de un tipo distinto deCancellationToken, - o si el
EnumeratorCancellationatributo se aplica a un parámetro en un método que no es un iterador asincrónico (§15.15), - o si el
EnumeratorCancellationatributo se aplica a un parámetro en un método que devuelve una interfaz enumerable asincrónica (§15.15.3) en lugar de una interfaz de enumerador asincrónica (§15.15.2).
El iterador no tendrá acceso al CancellationToken argumento para GetAsyncEnumerator cuando ningún atributo tenga este parámetro.
Ejemplo: El método
GetStringsAsync()es un iterador asincrónico. Antes de realizar cualquier trabajo para recuperar el siguiente valor, comprueba el token de cancelación para determinar si se debe cancelar la iteración. Si se solicita la cancelación, no se realiza ninguna otra acción.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(); } }ejemplo final
23.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 atributo IndexerName presente para un indexador, el nombre Item se usa de forma predeterminada. El atributo IndexerName permite que un desarrollador anule este valor predeterminado y especifique un nombre diferente.
Ejemplo: de forma predeterminada, el nombre de un indexador es
Item. Se puede anular de la forma siguiente:[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