Atributos reservados: varios

Estos atributos se pueden aplicar a los elementos del código. Agregan significado semántico a esos elementos. El compilador usa esos significados semánticos para modificar su salida e informar de los posibles errores por parte de los desarrolladores que usan el código.

Atributo Conditional

El atributo Conditional hace que la ejecución de un método dependa de un identificador de preprocesamiento. El atributo Conditional es un alias de ConditionalAttribute y se puede aplicar a un método o a una clase de atributo.

En el ejemplo siguiente, Conditional se aplica a un método para habilitar o deshabilitar la representación de información de diagnóstico específica del programa:

#define TRACE_ON
using System;
using System.Diagnostics;

namespace AttributeExamples
{
    public class Trace
    {
        [Conditional("TRACE_ON")]
        public static void Msg(string msg)
        {
            Console.WriteLine(msg);
        }
    }

    public class TraceExample
    {
        public static void Main()
        {
            Trace.Msg("Now in Main...");
            Console.WriteLine("Done.");
        }
    }
}

Si no se define el identificador TRACE_ON, no se muestra el resultado del seguimiento. Explore por sí mismo en la ventana interactiva.

El atributo Conditional se suele usar con el identificador DEBUG para habilitar las funciones de seguimiento y de registro para las compilaciones de depuración, pero no en las compilaciones de versión, como se muestra en el ejemplo siguiente:

[Conditional("DEBUG")]
static void DebugMethod()
{
}

Al llamar a un método marcado como condicional, la presencia o ausencia del símbolo de preprocesamiento especificado determina si el compilador incluye u omite la llamada al método. Si el símbolo está definido, se incluye la llamada; de lo contrario, se omite la llamada. Un método condicional debe ser un método de una declaración de clase o estructura, y no debe tener un tipo de valor devuelto void. El uso de Conditional resulta más limpio y elegante, y menos propenso a generar errores que incluir los métodos dentro de bloques #if…#endif.

Si un método tiene varios atributos Conditional, el compilador incluye llamadas al método si se define uno o más símbolos condicionales (los símbolos se vinculan de manera lógica entre sí mediante el operador OR). En el ejemplo siguiente, la presencia de A o B da como resultado una llamada al método:

[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
    // ...
}

Uso de Conditional con clases de atributos

El atributo Conditional también se puede aplicar a una definición de clase de atributo. En el ejemplo siguiente, el atributo personalizado Documentation solo agregará información a los metadatos si se define DEBUG.

[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
    string text;

    public DocumentationAttribute(string text)
    {
        this.text = text;
    }
}

class SampleClass
{
    // This attribute will only be included if DEBUG is defined.
    [Documentation("This method displays an integer.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

Atributo Obsolete

El atributo Obsolete marca un elemento de código como ya no recomendado para su uso. El uso de una entidad marcada como obsoleta genera una advertencia o un error. El atributo Obsolete es un atributo de uso único y se puede aplicar a cualquier entidad que admita atributos. Obsolete es un alias de ObsoleteAttribute.

En el ejemplo siguiente, el atributo Obsolete se aplica a la clase A y al método B.OldMethod. Dado que el segundo argumento del constructor de atributos aplicado a B.OldMethod está establecido en true, este método producirá un error del compilador, mientras que, si se usa la clase A, solo se generará una advertencia. En cambio, si se llama a B.NewMethod, no se generará ninguna advertencia o error. Por ejemplo, al usarla con las definiciones anteriores, el código siguiente genera dos advertencias y un error:

using System;

namespace AttributeExamples
{
    [Obsolete("use class B")]
    public class A
    {
        public void Method() { }
    }

    public class B
    {
        [Obsolete("use NewMethod", true)]
        public void OldMethod() { }

        public void NewMethod() { }
    }

    public static class ObsoleteProgram
    {
        public static void Main()
        {
            // Generates 2 warnings:
            A a = new A();

            // Generate no errors or warnings:
            B b = new B();
            b.NewMethod();

            // Generates an error, compilation fails.
            // b.OldMethod();
        }
    }
}

La cadena proporcionada como primer argumento al constructor del atributo se mostrará como parte de la advertencia o el error. Se generan dos advertencias para la clase A: una para la declaración de la referencia de clase y otra para el constructor de clases. El atributo Obsolete se puede usar sin argumentos, pero se recomienda incluir una explicación de qué se debe usar en su lugar.

En C# 10, puede usar la interpolación de cadenas constantes y el operador nameof para asegurarse de que los nombres coinciden:

public class B
{
    [Obsolete($"use {nameof(NewMethod)} instead", true)]
    public void OldMethod() { }

    public void NewMethod() { }
}

Atributo AttributeUsage

El atributo AttributeUsage determina cómo se puede usar una clase de atributo personalizado. AttributeUsageAttribute es un atributo que se aplica a las definiciones de atributo personalizado. El atributo AttributeUsage permite controlar lo siguiente:

  • Los atributos de elementos de programa que se pueden aplicar. A menos que el uso esté restringido, un atributo se puede aplicar a cualquiera de los siguientes elementos de programa:
    • Ensamblado
    • Módulo
    • Campo
    • Evento
    • Método
    • Parámetro
    • Propiedad
    • Valor devuelto
    • Tipo
  • Si un atributo se puede aplicar a un mismo elemento de programa varias veces.
  • Si las clases derivadas heredan atributos.

La configuración predeterminada se parece al siguiente ejemplo cuando se aplica explícitamente:

[AttributeUsage(AttributeTargets.All,
                   AllowMultiple = false,
                   Inherited = true)]
class NewAttribute : Attribute { }

En este ejemplo, la clase NewAttribute se puede aplicar a cualquier elemento de programación compatible, pero solamente se puede aplicar una vez a cada entidad. Las clases derivadas heredan el atributo cuando se aplica a una clase base.

Los argumentos AllowMultiple y Inherited son opcionales, por lo que el siguiente código tiene el mismo efecto:

[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }

El primer argumento AttributeUsageAttribute debe ser uno o varios elementos de la enumeración AttributeTargets. Se pueden vincular diversos tipos de destino con el operador OR, como se refleja en el siguiente ejemplo:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }

A partir de C# 7.3, los atributos se pueden aplicar a la propiedad o el campo de respaldo de una propiedad implementada automáticamente. El atributo se aplica a la propiedad, a menos que se indique el especificador field en el atributo. Ambos se muestran en el siguiente ejemplo:

class MyClass
{
    // Attribute attached to property:
    [NewPropertyOrField]
    public string Name { get; set; } = string.Empty;

    // Attribute attached to backing field:
    [field: NewPropertyOrField]
    public string Description { get; set; } = string.Empty;
}

Si el argumento AllowMultiple es true, el atributo resultante se puede aplicar más de una vez a cada una de las entidades, como se muestra en el siguiente ejemplo:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }

[MultiUse]
[MultiUse]
class Class1 { }

[MultiUse, MultiUse]
class Class2 { }

En este caso, MultiUseAttribute se puede aplicar varias veces porque AllowMultiple está establecido en true. Los dos formatos mostrados para aplicar varios atributos son válidos.

Si Inherited se establece en false, las clases que se derivan de una clase con atributos no heredan el atributo. Por ejemplo:

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }

[NonInherited]
class BClass { }

class DClass : BClass { }

En este caso, NonInheritedAttribute no se aplica a DClass a través de la herencia.

También puede usar estas palabras clave para especificar dónde se debe aplicar un atributo. Por ejemplo, puede usar el especificador field: para agregar un atributo al campo de respaldo de una propiedad implementada automáticamente. También puede usar el especificador field:, property: o param: para aplicar un atributo a cualquiera de los elementos generados a partir de un registro posicional. Para obtener un ejemplo, vea Sintaxis posicional para la definición de propiedades.

Atributo AsyncMethodBuilder

A partir de C# 7, se agrega el atributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute a un tipo que puede ser un tipo de valor devuelto asincrónico. El atributo especifica el tipo que compila la implementación del método asincrónico cuando se devuelve el tipo especificado desde un método asincrónico. El atributo AsyncMethodBuilder se puede aplicar a un tipo que:

El constructor del atributo AsyncMethodBuilder especifica el tipo del generador asociado. El generador debe implementar los siguientes miembros accesibles:

  • Un método Create() estático que devuelve el tipo del compilador.

  • Una propiedad Task legible que devuelve el tipo de valor devuelto asincrónico.

  • Un método void SetException(Exception) que establece la excepción cuando se produce un error en una tarea.

  • Un método void SetResult() o void SetResult(T result) que marca la tarea como completada y, opcionalmente, establece el resultado de la tarea.

  • Un método Start con la signatura de API siguiente:

    void Start<TStateMachine>(ref TStateMachine stateMachine)
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • un método AwaitOnCompleted con la signatura siguiente:

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • un método AwaitUnsafeOnCompleted con la signatura siguiente:

          public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
              where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    

Para obtener información sobre los generadores de métodos asincrónicos, lea sobre los generadores siguientes proporcionados por .NET:

En C# 10 y versiones posteriores, el atributo AsyncMethodBuilder se puede aplicar a un método asincrónico para invalidar el generador de ese tipo.

Atributos InterpolatedStringHandler y InterpolatedStringHandlerArguments

A partir de C# 10, se usan estos atributos para especificar que un tipo es un controlador de cadenas interpoladas. La biblioteca de .NET 6 ya incluye System.Runtime.CompilerServices.DefaultInterpolatedStringHandler para escenarios en los que se usa una cadena interpolada como argumento para un parámetro string. Es posible que tenga otras instancias en las que quiera controlar cómo se procesan las cadenas interpoladas. Aplique System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute al tipo que implementa el controlador. Aplique System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute a los parámetros del constructor de ese tipo.

Puede obtener más información sobre la creación de un controlador de cadenas interpoladas en la especificación de características de C# 10 sobre las mejoras de cadenas interpoladas.

Atributo ModuleInitializer

A partir de C# 9, el atributo ModuleInitializer marca un método al que el entorno de ejecución llama cuando se carga el ensamblado. ModuleInitializer es un alias de ModuleInitializerAttribute.

El atributo ModuleInitializer solo se puede aplicar a un método que:

  • Sea estático.
  • No tenga parámetros.
  • Devuelve void.
  • Sea accesible desde el módulo contenedor, es decir, internal o public.
  • No sea un método genérico.
  • No esté incluido en una clase genérica.
  • No sea una función local.

El atributo ModuleInitializer se puede aplicar a varios métodos. En ese caso, el orden de llamada desde el entorno de ejecución es determinista pero no se especifica.

En el ejemplo siguiente se muestra el uso de varios métodos de inicializador de módulo. Los métodos Init1 y Init2 se ejecutan antes que Main y cada uno agrega una cadena a la propiedad Text. Por tanto, cuando se ejecuta Main, la propiedad Text ya tiene cadenas de los dos métodos de inicializador.

using System;

internal class ModuleInitializerExampleMain
{
    public static void Main()
    {
        Console.WriteLine(ModuleInitializerExampleModule.Text);
        //output: Hello from Init1! Hello from Init2!
    }
}
using System.Runtime.CompilerServices;

internal class ModuleInitializerExampleModule
{
    public static string? Text { get; set; }

    [ModuleInitializer]
    public static void Init1()
    {
        Text += "Hello from Init1! ";
    }

    [ModuleInitializer]
    public static void Init2()
    {
        Text += "Hello from Init2! ";
    }
}

En ocasiones los generadores de código deben generar código de inicialización. Los inicializadores de módulos proporcionan una ubicación estándar para ese código.

Atributo SkipLocalsInit

A partir de C# 9, el atributo SkipLocalsInit impide que el compilador establezca la marca .locals init cuando se realiza la emisión a metadatos. SkipLocalsInit es un atributo de uso único y se puede aplicar a un método, una propiedad, una clase, una estructura, una interfaz o un módulo, pero no a un ensamblado. SkipLocalsInit es un alias de SkipLocalsInitAttribute.

La marca .locals init hace que el CLR inicialice todas las variables locales declaradas en un método en sus valores predeterminados. Como el compilador también se asegura de que nunca se use una variable antes de asignarle un valor, .locals init no suele ser necesario. Pero la inicialización en cero adicional puede afectar al rendimiento en algunos escenarios, como cuando se usa stackalloc para asignar una matriz en la pila. En esos casos, puede agregar el atributo SkipLocalsInit. Si se aplica directamente a un método, el atributo afecta a ese método y a todas sus funciones anidadas, incluidas las expresiones lambda y las funciones locales. Si se aplica a un tipo o un módulo, afecta a todos los métodos anidados. Este atributo no afecta a los métodos abstractos, pero sí al código generado para la implementación.

Este atributo necesita la opción del compilador AllowUnsafeBlocks. Este requisito indica que, en algunos casos, el código podría ver la memoria no asignada (por ejemplo, la lectura de la memoria asignada a la pila no inicializada).

En el ejemplo siguiente se muestra el efecto del atributo SkipLocalsInit en un método que usa stackalloc. El método muestra lo que hubiera en la memoria al asignar la matriz de enteros.

[SkipLocalsInit]
static void ReadUninitializedMemory()
{
    Span<int> numbers = stackalloc int[120];
    for (int i = 0; i < 120; i++)
    {
        Console.WriteLine(numbers[i]);
    }
}
// output depends on initial contents of memory, for example:
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
// Remaining rows omitted for brevity.

Para probar este código, establezca la opción del compilador AllowUnsafeBlocks en el archivo .csproj:

<PropertyGroup>
  ...
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Vea también