Condividi tramite


Definire e leggere attributi personalizzati

Gli attributi consentono di associare le informazioni al codice in modo dichiarativo. Possono anche fornire un elemento riutilizzabile che può essere applicato a varie destinazioni. Si consideri ObsoleteAttribute. Può essere applicato a classi, struct, metodi, costruttori e altro ancora. Dichiara che l'elemento è obsoleto. Spetta quindi al compilatore C# cercare questo attributo ed eseguire alcune azioni in risposta.

In questa esercitazione si apprenderà come aggiungere attributi al codice, come creare e usare attributi personalizzati e come usare alcuni attributi incorporati in .NET.

Prerequisiti

È necessario configurare il computer per eseguire .NET. Le istruzioni di installazione sono disponibili nella pagina Download di .NET . È possibile eseguire questa applicazione in Windows, Ubuntu Linux, macOS o in un contenitore Docker. È necessario installare l'editor di codice preferito. Le descrizioni seguenti usano Visual Studio Code, un editor open source multipiattaforma. Tuttavia, è possibile usare qualsiasi strumento con cui si ha familiarità.

Creare l'app

Dopo aver installato tutti gli strumenti, creare una nuova app console .NET. Per usare il generatore della riga di comando, eseguire il comando seguente nella shell preferita:

dotnet new console

Questo comando crea file di progetto .NET minimali. Esegui dotnet restore per ripristinare le dipendenze necessarie per compilare questo progetto.

Non è necessario eseguire dotnet restore perché viene eseguito in modo implicito da tutti i comandi che richiedono un ripristino, ad esempio dotnet new, dotnet build, dotnet run, dotnet test, dotnet publish e dotnet pack. Per disabilitare il ripristino implicito, usare l'opzione --no-restore.

Il comando dotnet restore è ancora utile in alcuni scenari in cui ha senso eseguire un ripristino esplicito, ad esempio le compilazioni di integrazione continua in Azure DevOps Services o in sistemi di compilazione che richiedono il controllo esplicito quando viene eseguito il ripristino.

Per informazioni su come gestire i feed NuGet, vedere la dotnet restore documentazione.

Per eseguire il programma, usare dotnet run. Dovresti vedere l'output "Hello, World" nella console.

Aggiungere attributi al codice

In C# gli attributi sono classi che ereditano dalla Attribute classe di base. Qualsiasi classe che eredita da Attribute può essere usata come tipo di "tag" in altre parti di codice. Ad esempio, è presente un attributo denominato ObsoleteAttribute. Questo attributo segnala che il codice è obsoleto e non deve più essere usato. Questo attributo viene inserito in una classe, ad esempio usando le parentesi quadre.

[Obsolete]
public class MyClass
{
}

Anche se la classe è denominata ObsoleteAttribute, è necessario usare [Obsolete] solo nel codice. La maggior parte del codice C# segue questa convenzione. Se si sceglie, è possibile usare il nome [ObsoleteAttribute] completo.

Quando si contrassegna una classe obsoleta, è consigliabile fornire alcune informazioni sul motivo per cui è obsoleto e/o cosa usare. Includere un parametro stringa per l'attributo Obsoleto per fornire questa spiegazione.

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

La stringa viene passata come argomento a un ObsoleteAttribute costruttore, come se si scrivesse var attr = new ObsoleteAttribute("some string").

I parametri di un costruttore di attributi sono limitati a tipi/valori letterali semplici: bool, int, double, string, Type, enums, etc e matrici di tali tipi. Non è possibile usare un'espressione o una variabile. È possibile usare parametri posizionali o denominati.

Creare un attributo personalizzato

Per creare un attributo, definire una nuova classe che eredita dalla Attribute classe di base.

public class MySpecialAttribute : Attribute
{
}

Con il codice precedente, è possibile usare [MySpecial] (o [MySpecialAttribute]) come attributo altrove nella codebase.

[MySpecial]
public class SomeOtherClass
{
}

Attributi nella libreria di classi di base .NET, ad esempio ObsoleteAttribute attivare determinati comportamenti all'interno del compilatore. Tuttavia, qualsiasi attributo creato agisce solo come metadati e non comporta l'esecuzione di codice all'interno della classe di attributi. Spetta all'utente agire su tali metadati altrove nel codice.

C'è un problema imprevisto di cui fare attenzione. Come accennato in precedenza, solo alcuni tipi possono essere passati come argomenti quando si usano attributi. Tuttavia, quando si crea un tipo di attributo, il compilatore C# non impedisce di creare tali parametri. Nell'esempio seguente, hai creato un attributo con un costruttore che compila correttamente.

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

Tuttavia, non è possibile usare questo costruttore con la sintassi degli attributi.

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

Il codice precedente causa un errore del compilatore, ad esempio Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type

Come limitare l'utilizzo degli attributi

Gli attributi possono essere usati nelle "destinazioni" seguenti. Gli esempi precedenti li mostrano sulle classi, ma possono anche essere usati in:

  • Assemblea
  • Classe
  • Costruttore
  • Delegare
  • Enumerazione
  • Evento
  • Campo
  • ParametroGenerico
  • Interfaccia
  • Metodo
  • Modulo
  • Parametro
  • Proprietà
  • Valore di Ritorno
  • Struttura

Quando si crea una classe di attributi, per impostazione predefinita, C# consente di usare tale attributo in una delle possibili destinazioni di attributo. Se si vuole limitare l'attributo a determinate destinazioni, è possibile farlo usando nella classe dell'attributo AttributeUsageAttribute . Esatto, un attributo che si applica a un altro attributo!

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

Se si tenta di inserire l'attributo precedente in un elemento che non è una classe o uno struct, viene visualizzato un errore del compilatore, ad esempio 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()
    { }
}

Come usare gli attributi associati a un elemento di codice

Gli attributi fungono da metadati. Senza una forza esterna, non fanno nulla.

Per trovare e agire sugli attributi, è necessaria la riflessione. La reflection consente di scrivere codice in C# che esamina altro codice. Ad esempio, è possibile usare Reflection per ottenere informazioni su una classe (aggiungere using System.Reflection; all'inizio del codice):

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

Che stampa qualcosa come: The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Dopo aver creato un TypeInfo oggetto (o un MemberInfooggetto , FieldInfoo un altro oggetto ), è possibile usare il GetCustomAttributes metodo . Questo metodo restituisce una raccolta di Attribute oggetti . È anche possibile usare GetCustomAttribute e specificare un tipo di attributo.

Ecco un esempio di come usare GetCustomAttributes su un'istanza MemberInfo per MyClass (che abbiamo visto prima ha un attributo [Obsolete] su di esso).

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

Stampa nella console: Attribute on MyClass: ObsoleteAttribute. Provare ad aggiungere altri attributi a MyClass.

È importante notare che questi Attribute oggetti vengono istanziati pigramente. Quindi, non vengono create istanze finché non si usa GetCustomAttribute o GetCustomAttributes. Vengono istanziate anche ogni volta. Chiamando GetCustomAttributes due volte consecutivamente restituisce due istanze diverse di ObsoleteAttribute.

Attributi comuni nel runtime

Gli attributi vengono usati da molti strumenti e framework. NUnit usa attributi come [Test] e [TestFixture] usati dallo strumento di esecuzione di test NUnit. ASP.NET MVC usa attributi come [Authorize] e fornisce un framework di filtro delle azioni per eseguire aspetti trasversali sulle azioni MVC. PostSharp usa la sintassi degli attributi per consentire la programmazione orientata agli aspetti in C#.

Ecco alcuni attributi rilevanti incorporati nelle librerie di classi di base di .NET Core:

  • [Obsolete]. Questo è stato usato negli esempi precedenti e si trova nello spazio dei nomi System. È utile fornire documentazione dichiarativa su una codebase che cambia. Un messaggio può essere fornito sotto forma di stringa e un altro parametro booleano può essere usato per eseguire l'escalation da un avviso del compilatore a un errore del compilatore.
  • [Conditional]. Questo attributo si trova nello spazio dei nomi System.Diagnostics. Questo attributo può essere applicato ai metodi (o alle classi di attributi). È necessario passare una stringa al costruttore . Se tale stringa non corrisponde a una #define direttiva, il compilatore C# rimuove tutte le chiamate a tale metodo (ma non il metodo stesso). In genere si usa questa tecnica per scopi di debug (diagnostica).
  • [CallerMemberName]. Questo attributo può essere usato nei parametri e si trova nello spazio dei nomi System.Runtime.CompilerServices. CallerMemberName è un attributo utilizzato per inserire il nome del metodo che chiama un altro metodo. È un modo per eliminare le "stringhe magiche" quando si implementa INotifyPropertyChanged in vari framework di interfaccia utente. Ad esempio:
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
            }
        }
    }
}

Nel codice precedente non è necessario avere una stringa letterale "Name" . L'uso di CallerMemberName impedisce i bug legati agli errori di digitazione e rende anche più semplice il refactoring e la rinominazione. Gli attributi portano potenza dichiarativa a C#, ma sono una forma di metadati di codice e non agiscono da soli.