Atrybuty - jak i po co?

Czasami pisząc kod stwierdzamy, że chcemy w jakiś sposób opisać nasze typy w taki sposób, aby można było się do tego odnieść w kodzie. Z pomocą przychodzą nam dwie funkcjonalności .NETu: Reflection i atrybuty.

Czym są atrybuty?

Na poziomie deklaracji, atrybut to nic więcej jak klasa dziedzicząca po System.Attribute. Możemy chcieć ,,pusty” atrybut, czyli taki który po prostu jakoś oznacza pewien typ/członka, lub możemy dodać mu własności. Może jakiś prosty przykład:

class A
{
    [Obsolete]
    public void DoStuff() {}
}

Więc, jak można by się domyślić, oznaczyliśmy naszą metodę DoStuff() atrybutem ObsoleteAttribute, bez dodatkowych opcji. W tym wypadku kompilator zwróci nam uwagę, żeby tej metody nie używać.

Atrybuty mogą mieć wiele zastosowań oraz być przypisywane do różnych elementów. Możemy rozróżnić cele (,,target”) atrybutów nad metodą określając ten cel przed nazwą atrybutu:

    [method: MyMethod]
    [return: MyReturn]
    public int Do([param: MyParam] string name) {}

Przy czym jeszcze nie miałem okazji wykorzystać tak określone atrybuty. Pełna lista celów znajduje się tutaj.

Jak mogliście zauważyć napisałem wcześniej ObsoleteAttribute, ale w kodzie jest [Obsolete]. Dla wygody można nie pisać końcówki Attribute, która powinna znaleźć się na końcu nazwy każdego atrybutu.

Deklaracja atrybutu

Rzućmy okiem na mój atrybut określający nazwę pliku konfiguracyjnego dla danego typu implementującego IConfiguration.

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ConfigurationSourceAttribute : Attribute
{
    private readonly string _sourcePath;

    public ConfigurationSourceAttribute(string sourcePath)
    {
        _sourcePath = sourcePath;
    }

    public string GetSourcePath()
    {
        return _sourcePath;
    }
}

Po pierwsze oznaczamy nasze atrybuty atrybutem [AttributeUsage], który pozwala określić co możemy oznaczyć naszym atrybutem oraz czy możemy mieć kilka tych samych atrybutów obok siebie i czy podklasy dziedziczą atrybut.

To co podamy jako argumenty w konstruktorze będzie wymagane, aby oznaczyć typ naszym atrybutem.

[ConfigurationSource("./app.config")]

Gdybym miał tu własności, to mógłbym się do nich odwoływać po przecinkach wewnątrz nawiasu w deklaracji atrybutu, np:

[Display(Name = "Przykład", Description = "Przykład użycia atrybutów"]
Jak wykorzystać atrybuty?

Część atrybutów, tak jak [Obsolete], jest wykorzystywana przez kompilator. Żeby samemu się odwołać do atrybutów trzeba zaimportować System.Reflection, aby mieć dostęp do metody rozszerzającej GetCustomAttribute(s)<>().

Jeszcze jedna ważna uwaga: atrybuty są przypisane do typów/członków, a nie do obiektów (instancji). Może się to wydawać mylące na początku, ale w miarę używania atrybutów można przywyknąć.

using System.Reflection;
...
    Type t = myVar.GetType();
    Type t2 = typeof(MyClass);
    var superAttribute = t.GetCustomAttribute<SuperAttribute>(); //if not found returns null
    var otherAttribute = t2.GetMethods().First().GetCustomAttribute<MyMethodAttribute>();

W tym kodzie chciałem zaprezentować jak wyciągnąć atrybut z typu oraz z metody, oraz jak wyciągnąć typ z obiektu/nazwy klasy.

Do czego wykorzystuję atrybuty w moim projekcie (SharpOffice)?

Na ten moment nie mam jeszcze wielu atrybutów, ale te które mam mogę opisać.

Atrybuty określające czy rejestrować z danej biblioteki typy do kontenera:

  • ApplicationAssemblyAttribute - biblioteki oznaczone tym atrybutem zawierają opis poszczególnych aplikacji pakietu,
  • CoreAssemblyAttribute - biblioteki bazowe,
  • PluginAssemblyAttibute - biblioteki rozszerzające funkcjonalność aplikacji.

Do tego mam WindowAttribute, który określa czy typ okna MainWindow | Window | Dialog. ApplicationAttribute służy do oznaczania głównej klasy w aplikacji, implementującej IApplication.

No i jeszcze dwa atrybuty do klas konfiguracji: ConfigurationSourceAttribute, określający nazwę pliku konfiguracji, i ConfigurationFormatAttribute, określający typ implementujący IConfigurationFormat w jakim dana konfiguracja ma być zapisywana.

Podsumowanie

Atrybuty to naprawdę przydatne narzędzie, jeśli je prawidłowo wykorzystać 😉