Compartir a través de


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, internaly 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.Attributeabstracta , 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 del Simple 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 el Attribute sufijo, lo que da como resultado el nombre Simplecorto . 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 del Author 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, urly un parámetro con nombre, Topic. Aunque no es estático y público, la propiedad Url 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, , charfloatdoublebyteshortlongintsbyte, , 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, methodparam, property, , return, typey 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 de System.Attribute , el tipo es el resultado de este paso.

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 y ExampleAttribute. El atributo [Example] es ambiguo, ya que podría hacer referencia a Example o ExampleAttribute. 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 denominada ExampleAttributeAttribute!). Si se quita la declaración de la clase Example , ambos atributos hacen referencia a la clase de atributo denominada ExampleAttribute, 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 de Class1.

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:
    • Valor constante
    • Objeto System.Type obtenido con un typeof_expression (§12.8.18) que especifica un tipo no genérico, un tipo construido cerrado (§8.4.3) o un tipo genérico no enlazado (§8.4.4), pero no un tipo abierto (§8.4.3).
    • Matriz unidimensional de attribute_argument_expressions.

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 Tde 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 constructor C de instancia en T 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 en N:
    • Vamos Name a ser el identificador del named_argument Arg.
    • Name identificará un campo público de lectura y escritura no estático o una propiedad en T. Si T no tiene este campo o propiedad, se produce un error en tiempo de compilación.
  • Si alguno de los valores de positional_argument_listP o uno de los valores de named_argument_listN es de tipo System.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 Tde atributo , el constructor C de instancia en T, el positional_argument_list P, el named_argument_listN y la entidad Ede 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, Py 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 constructor C de instancia y los valores que se determinan en tiempo de compilación. Estos pasos dan como resultado una excepción o generan una instancia O de T.
  • Para cada named_argument Arg en N, en orden:
    • Vamos Name a ser el identificador del named_argument Arg. Si Name no identifica un campo o propiedad de lectura y escritura públicos no estáticos en O, se produce una excepción.
    • Vamos Value a ser el resultado de evaluar la attribute_argument_expression de Arg.
    • Si Name identifica un campo en O, establezca este campo Valueen .
    • De lo contrario, Name identifica una propiedad en O. Establezca esta propiedad en Value.
    • El resultado es O, una instancia de la clase T de atributo que se ha inicializado con el positional_argument_list P y el named_argument_listN.

Nota: El formato para almacenar T, C, P, N (y asociarlo a E) en A y el mecanismo para especificar E y recuperar T, C, , Pde A (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) y System.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ímbolos ALPHA de compilación condicional y BETA.

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 el virtual modificador . Las invalidaciones de este método son condicionales implícitamente y no se marcarán explícitamente con un Conditional 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. Class2El método llama Test a este método. Dado que se define el símbolo DEBUG de compilación condicional, si Class2.Test se llama a , llamará a M. Si no se hubiera definido el símbolo DEBUG , Class2.Test no llamaría a Class1.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 y Class3 cada una contienen llamadas al método Class1.Fcondicional , que es condicional en función de si se define o no DEBUG . Dado que este símbolo se define en el contexto de Class2 , pero no Class3, se incluye la llamada a F en Class2 , mientras que la llamada a F en Class3 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 basede , 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 la M definida en su clase base. Esta llamada se omite porque el método base está condicional en función de la presencia del símbolo DEBUG, 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 condicionales ALPHA y BETA.

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 atributo Test, que es condicional en función de si se define o noDEBUG.Class2 Dado que este símbolo se define en el contexto de Class1 , pero no Class2, se incluye la especificación del atributo Test en Class1 , mientras que se omite la especificación del Test atributo en Class2 .

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 el Obsolete atributo . Cada uso de A en Main da como resultado una advertencia que incluye el mensaje especificado, "Esta clase está obsoleta; use la clase B 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, , CallerMemberNameCallerFilePath. 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 por string, T? se convierte en una anotación que acepta valores NULL. Sin embargo, este código no es legal porque T 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 nullrealmente . 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 el value argumento no nullsea . Sin embargo, es aceptable llamar ThrowWhenNull 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 nulles .

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 el NotNullIfNotNull atributo se puede usar. Observe el método siguiente:

#nullable enable
string GetTopLevelDomainFromFullUrl(string url) { ... }

Si el url argumento no nulles , 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 devuelve true cuando el argumento es null 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 devuelve false. 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