Compartir a través de


Definición y lectura de atributos personalizados

Los atributos proporcionan una manera de asociar información con código de forma declarativa. También pueden proporcionar un elemento reutilizable que se puede aplicar a varios destinos. Tenga en cuenta ObsoleteAttribute. Se puede aplicar a clases, estructuras, métodos, constructores, etc. Declara que el elemento está obsoleto. A continuación, es necesario que el compilador de C# busque este atributo y realice alguna acción en respuesta.

En este tutorial, aprenderá a agregar atributos al código, a crear y usar sus propios atributos y a usar algunos atributos integrados en .NET.

Prerrequisitos

Debe configurar la máquina para ejecutar .NET. Puede encontrar las instrucciones de instalación en la página Descargas de .NET . Puede ejecutar esta aplicación en Windows, Ubuntu Linux, macOS o en un contenedor de Docker. Debe instalar el editor de código favorito. Las descripciones siguientes usan Visual Studio Code, que es un editor multiplataforma de código abierto. Sin embargo, puede usar las herramientas con las que se sienta cómodo.

Creación de la aplicación

Ahora que ha instalado todas las herramientas, cree una nueva aplicación de consola de .NET. Para usar el generador de línea de comandos, ejecute el siguiente comando en el shell favorito:

dotnet new console

Este comando crea archivos de proyecto básicos de .NET. Ejecute dotnet restore para restaurar las dependencias necesarias para compilar este proyecto.

No es necesario ejecutar dotnet restore porque se ejecuta implícitamente por todos los comandos que requieren que se produzca una restauración, tales como dotnet new, dotnet build, dotnet run, dotnet test, dotnet publish y dotnet pack. Para deshabilitar la restauración implícita, use la opción --no-restore.

El comando dotnet restore sigue siendo útil en determinados escenarios en los que la restauración explícitamente tiene sentido, como compilaciones de integración continua en Azure DevOps Services o en sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.

Para obtener información sobre cómo administrar fuentes de NuGet, consulte la documentación de dotnet restore.

Para ejecutar el programa, use dotnet run. Deberá ver la salida "Hola a todos" a la consola.

Adición de atributos al código

En C#, los atributos son clases que heredan de la Attribute clase base. Cualquier clase que herede de Attribute se puede usar como una especie de "etiqueta" en otros fragmentos de código. Por ejemplo, hay un atributo denominado ObsoleteAttribute. Este atributo indica que el código está obsoleto y que ya no se debe usar. Este atributo se coloca en una clase, por ejemplo, mediante corchetes.

[Obsolete]
public class MyClass
{
}

Aunque la clase se llame ObsoleteAttribute, solo es necesario usar [Obsolete] en el código. La mayoría del código de C# sigue esta convención. Si lo desea, puede usar el nombre [ObsoleteAttribute] completo.

Al marcar una clase obsoleta, es una buena idea proporcionar información sobre por qué está obsoleta o qué usar en su lugar. Se incluye un parámetro de cadena en el atributo Obsoleto para proporcionar esta explicación.

[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
public class ThisClass
{
}

La cadena se pasa como argumento a un ObsoleteAttribute constructor, como si estuviera escribiendo var attr = new ObsoleteAttribute("some string").

Los parámetros de un constructor de atributos se limitan a tipos o literales simples: bool, int, double, string, Type, enums, etc y matrices de esos tipos. No se puede usar una expresión ni una variable. Es libre de usar parámetros posicionales o con nombre.

Creación de su propio atributo

Para crear un atributo, defina una nueva clase que herede de la Attribute clase base.

public class MySpecialAttribute : Attribute
{
}

Con el código anterior, puede usar [MySpecial] (o [MySpecialAttribute]) como atributo en otra parte del código base.

[MySpecial]
public class SomeOtherClass
{
}

Atributos como ObsoleteAttribute de la biblioteca de clases base de .NET desencadenan determinados comportamientos dentro del compilador. Sin embargo, cualquier atributo que cree solo actúa como metadatos y no da lugar a que se ejecute ningún código dentro de la clase de atributo. Depende de usted actuar sobre esos metadatos en otro lugar del código.

Aquí hay un problema que se debe vigilar. Como se mencionó anteriormente, solo se pueden pasar determinados tipos como argumentos al usar atributos. Sin embargo, al crear un tipo de atributo, el compilador de C# no le impide crear esos parámetros. En el ejemplo siguiente, ha creado un atributo con un constructor que se compila correctamente.

public class GotchaAttribute : Attribute
{
    public GotchaAttribute(Foo myClass, string str)
    {
    }
}

Sin embargo, no puede usar este constructor con una sintaxis de atributo.

[Gotcha(new Foo(), "test")] // does not compile
public class AttributeFail
{
}

El código anterior provoca un error del compilador, como Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type

Cómo restringir el uso de atributos

Los atributos se pueden usar en los siguientes "objetivos". Los ejemplos anteriores los muestran en las clases, pero también se pueden usar en:

  • Asamblea
  • Clase
  • Constructor
  • Delegar
  • Enumeración
  • Evento
  • Campo
  • ParámetroGenérico
  • Interfaz
  • Método
  • Módulo
  • Parámetro
  • Propiedad
  • ReturnValue
  • Estructura

Cuando se crea una clase de atributo, de forma predeterminada, C# permite usar ese atributo en cualquiera de los destinos de atributo posibles. Si desea restringir su atributo a determinados destinos, puede hacerlo mediante el uso de AttributeUsageAttribute en su clase de atributo. Así es, ¡un atributo sobre un atributo!

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}

Si intenta colocar el atributo anterior en algo que no es una clase o una estructura, obtendrá un error del compilador como Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class, struct' declarations

public class Foo
{
    // if the below attribute was uncommented, it would cause a compiler error
    // [MyAttributeForClassAndStructOnly]
    public Foo()
    { }
}

Uso de atributos asociados a un elemento de código

Los atributos actúan como metadatos. Sin alguna fuerza exterior, realmente no hacen nada.

Para buscar y actuar sobre atributos, se necesita reflexión. La reflexión permite escribir código en C# que examina otro código. Por ejemplo, puede usar Reflection para obtener información sobre una clase (agregar using System.Reflection; al principio del código):

TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);

Esto imprime algo parecido a: The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Una vez que tenga un objeto TypeInfo (o MemberInfo, FieldInfo u otro objeto), puede usar el método GetCustomAttributes. Este método devuelve una colección de Attribute objetos . También puede usar GetCustomAttribute y especificar un tipo de atributo.

Este es un ejemplo del uso de GetCustomAttributes en una instancia de MemberInfo para MyClass (que anteriormente vimos que contiene un atributo [Obsolete]).

var attrs = typeInfo.GetCustomAttributes();
foreach(var attr in attrs)
    Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);

Se muestra lo siguiente en la consola: Attribute on MyClass: ObsoleteAttribute. Intente agregar otros atributos a MyClass.

Es importante tener en cuenta que se crean instancias de estos objetos Attribute de forma diferida. Es decir, no se instancian hasta que utilices GetCustomAttribute o GetCustomAttributes. También se crea una instancia de ellos cada vez. Al llamar GetCustomAttributes dos veces en una fila, se devuelven dos instancias diferentes de ObsoleteAttribute.

Atributos comunes en el entorno de ejecución

Muchos marcos y herramientas usan atributos. NUnit usa atributos como [Test] y [TestFixture] que usa el ejecutor de pruebas de NUnit. ASP.NET MVC usa atributos como [Authorize] y proporciona un marco de trabajo de filtro de acciones para gestionar aspectos transversales en las acciones del MVC. PostSharp usa la sintaxis de atributo para permitir la programación orientada a aspectos en C#.

Estos son algunos atributos importantes integrados en las bibliotecas de clases base de .NET Core:

  • [Obsolete]. Esta se utilizó en los ejemplos previos y reside en el System espacio de nombres. Resulta útil proporcionar documentación declarativa sobre una base de código cambiante. Se puede proporcionar un mensaje en forma de cadena y se puede usar otro parámetro booleano para escalar de una advertencia del compilador a un error del compilador.
  • [Conditional]. Este atributo se encuentra dentro del espacio de nombres System.Diagnostics. Este atributo se puede aplicar a métodos (o clases de atributos). Debe pasar una cadena al constructor. Si esa cadena no coincide con una #define directiva, el compilador de C# quita las llamadas a ese método (pero no al propio método). Normalmente, se usa esta técnica con fines de depuración y diagnóstico.
  • [CallerMemberName]. Este atributo se puede usar en parámetros y reside en el System.Runtime.CompilerServices espacio de nombres . CallerMemberName es un atributo que se usa para insertar el nombre del método que llama a otro método. Es una manera de eliminar "cadenas mágicas" al implementar INotifyPropertyChanged en varios marcos de interfaz de usuario. Por ejemplo:
public class MyUIClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public void RaisePropertyChanged([CallerMemberName] string propertyName = default!)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string? _name;
    public string? Name
    {
        get { return _name;}
        set
        {
            if (value != _name)
            {
                _name = value;
                RaisePropertyChanged();   // notice that "Name" is not needed here explicitly
            }
        }
    }
}

En el código anterior, no es necesario tener una cadena literal "Name" . El uso de CallerMemberName evita los errores relacionados con los tipos y también agiliza los procesos de refactorización o cambio de nombre. Los atributos aportan potencia declarativa a C#, pero son una forma de metadatos de código y no actúan por sí mismos.