Varios atributos interpretados por el compilador de C#
Hay varios atributos que se pueden aplicar a los elementos en su código que agregan significado semántico a esos elementos:
Conditional
: hace que la ejecución de un método dependa de un identificador de preprocesador.Obsolete
: marca un tipo o miembro para una (potencial) eliminación futura.AttributeUsage
: declara los elementos del lenguaje en los que se puede aplicar un atributo.AsyncMethodBuilder
: declara un tipo de generador de métodos asincrónico.InterpolatedStringHandler
: define un generador de cadenas interpoladas para un escenario conocido.ModuleInitializer
: declara un método que inicializa un módulo.SkipLocalsInit
: omite el código que inicializa el almacenamiento de variables locales a 0.UnscopedRef
: declara que una variableref
que normalmente se interpreta comoscoped
debe tratarse como sin ámbito.OverloadResolutionPriority
: agregue un atributo de desempate para influir en la resolución de sobrecargas para sobrecargas posiblemente ambiguas.Experimental
: marca un tipo o miembro como experimental.
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.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
agrega 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
se establece en true
, este método produce un error del compilador, mientras que el uso de la clase A
genera 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:
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 muestra 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 Experimental
Desde C# 12, los tipos, métodos y ensamblados se pueden marcar con el System.Diagnostics.CodeAnalysis.ExperimentalAttribute para indicar una característica experimental. El compilador emite una advertencia si tiene acceso a un método o tipo anotado con el ExperimentalAttribute. Todos los tipos declarados en un ensamblado o módulo marcados con el atributo Experimental
son experimentales. El compilador emite una advertencia si accede a cualquiera de ellos. Puede deshabilitar estas advertencias para probar una característica experimental.
Advertencia
Las características experimentales están sujetas a cambios. Las API pueden cambiar o se pueden quitar en futuras actualizaciones. Incluir características experimentales es una manera de que los autores de bibliotecas obtengan comentarios sobre ideas y conceptos para el desarrollo futuro. Tenga precaución extrema al usar cualquier característica marcada como experimental.
Puede leer más detalles sobre el atributo Experimental
en la especificación de características.
Atributo SetsRequiredMembers
El atributo SetsRequiredMembers
informa al compilador de que un constructor establece todos los miembros required
de esa clase o estructura. El compilador supone que cualquier constructor con el atributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute inicializa todos los miembros required
. Cualquier código que invoque este constructor no necesita inicializadores de objeto para establecer los miembros necesarios. Agregar el atributo SetsRequiredMembers
es principalmente útil para registros posicionales y constructores principales.
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:
- A qué elementos de programa se puede aplicar el atributo. A menos que restrinja su uso, se puede aplicar un atributo a cualquiera de los siguientes elementos del 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 aplicado 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 { }
Los atributos se pueden aplicar a la propiedad o al campo de respaldo de una propiedad autoimplementada. 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 es false
, las clases derivadas no heredan el atributo de una clase base con atributos. 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
mediante la herencia.
También puede usar estas palabras clave para especificar dónde se debe aplicar un atributo. Por ejemplo, puede usar el field:
especificador 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
El atributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute se agrega 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:
- Tiene un método
GetAwaiter
accesible. - El objeto devuelto por el método
GetAwaiter
implementa la interfaz System.Runtime.CompilerServices.ICriticalNotifyCompletion.
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()
ovoid 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:
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
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
El atributo ModuleInitializer
marca un método al que llama el tiempo de ejecución 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
opublic
. - 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. En la mayoría de los demás casos, debe escribir un constructor estático en lugar de un inicializador de módulo.
Atributo SkipLocalsInit
El atributo SkipLocalsInit
impide que el compilador establezca la marca .locals init
al emitir los 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. Sin embargo, la inicialización cero adicional podría tener un impacto medible en el 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>
Atributo UnscopedRef
El atributo UnscopedRef
marca una declaración de variable como sin ámbito, lo que significa que se permite que la referencia se escape.
Agregue este atributo donde el compilador trata un ref
como implícitamente scoped
:
- El parámetro
this
para los métodos de instancia destruct
. ref
parámetros que hacen referencia a tipos deref struct
.- Parámetros
out
.
Aplicar el System.Diagnostics.CodeAnalysis.UnscopedRefAttribute marca el elemento como sin ámbito.
Atributo OverloadResolutionPriority
OverloadResolutionPriorityAttribute permite a los autores de bibliotecas preferir una sobrecarga a otra cuando dos sobrecargas pueden ser ambiguas. Su uso principal es que los autores de bibliotecas puedan escribir sobrecargas de mejor rendimiento sin que se interrumpa el código existente.
Por ejemplo, puede agregar una nueva sobrecarga que use ReadOnlySpan<T> para reducir las asignaciones de memoria:
[OverloadResolutionPriority(1)]
public void M(params ReadOnlySpan<int> s) => Console.WriteLine("Span");
// Default overload resolution priority of 0
public void M(params int[] a) => Console.WriteLine("Array");
La resolución de sobrecargas considera que los dos métodos son igual de buenos para algunos tipos de argumentos. Para un argumento de int[]
, prefiere la primera sobrecarga. Para que el compilador prefiera la versión ReadOnlySpan
, puede aumentar la prioridad de esa sobrecarga. En el ejemplo siguiente se muestra el efecto de agregar el atributo:
var d = new OverloadExample();
int[] arr = [1, 2, 3];
d.M(1, 2, 3, 4); // Prints "Span"
d.M(arr); // Prints "Span" when PriorityAttribute is applied
d.M([1, 2, 3, 4]); // Prints "Span"
d.M(1, 2, 3, 4); // Prints "Span"
Todas las sobrecargas con una prioridad menor que la prioridad de sobrecarga más alta se quitan del conjunto de métodos aplicables. Los métodos sin este atributo tienen la prioridad de sobrecarga establecida en el valor predeterminado de cero. Los autores de bibliotecas deben usar este atributo como último recurso al agregar una sobrecarga de método nueva y mejor. Los autores de bibliotecas deben tener una comprensión profunda de cómo afecta la resolución de sobrecargas al elegir el mejor método. De lo contrario, pueden producirse errores inesperados.