Atrybut to znacznik, który służy do przekazywania informacji do środowiska wykonawczego o zachowaniu różnych elementów, takich jak: klasy, metody, struktury, typy wyliczeniowe czy poszczególne podzespoły naszego programu. Informacje takie mogą zostać zdeklarowane przy użyciu atrybutów. Atrybuty znajdują się pomiędzy nawiasami kwadratowymi.


Atrybuty służą do dodawania metadanych takich jak instrukcje dla kompilatora, komentarze, opisy metod oraz klas w naszym programie. Platforma .NET udostępnia dwa rodzaje atrybutów:

  • atrybuty predefiniowane;
  • atrybuty niestandardowe.


Definiowanie atrybutów

Składnia definicji atrybutu:
[Atrybut(parametry_wskazujace, wlasciowosci = value, ...)]
element

Nazwa atrybutu i jego wartości podawane są w nawiasach kwadratowych, przed elementem, do którego ten atrybut chcemy zastosować. Parametry wskazujące określają podstawowe informacje (wskazanie elementów aplikacji do których można dołączyć atrybuty) a właściowości określają dodatkowe informacje (np. czy atrybuty mogą być dziedziczone).


Predefiniowane atrybuty

Środowisko .NET dostarcza trzy predefiniowane atrybuty:

  • AtributeUsage;
  • Conditional;
  • Obsolete.


AttributeUsage

Atrybut AttributeUsage opisuje sposób użycia innego atrybutu klasy. Określa on typ elementów, które mogą ten atrybut zastosować.

Składnia definicji atrybutu:
[AttributeUsage(
    validon,
    AllowMultiple = allowMultiple,
    Inherited =inherited)]

gdzie,

  • parametr validon określa elementy aplikacji do której można przypisać atrybuty. Jest to kombinacja wartości z typu wyliczeniowego AttributeTargets. Domyślna wartość to AttributeTargets.All;
  • parametr allowmultiple jest parametrem opcjonalnym i stanowi wartość dla właściowości AllowMultiple, która jest wartością logiczną określającą czy do danego elementu można przypisać więcej niż jeden atrybut. Domyślna wartość to false;
  • parametr inherited jest parametrem opcjonalnym i stanowi wartość dla właściowości Inherited, która jest wartością logiczną. Jeżeli jest prawdziwa, atrybut jest dziedziczony z klasy pochodnej. Domyślna wartość to false.

Przykład użycia atrybutu:
using System;
namespace Atrybuty
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
    // Atrybut, który może być dołączony do 
    // klasy, konstruktora, pola, właściowości
    [AttributeUsage(AttributeTargets.Class |
        AttributeTargets.Constructor |
        AttributeTargets.Field |
        AttributeTargets.Property,
        AllowMultiple = true)]
    class HelpAttribute : Attribute
    {
        // Klasa, która używa atrybutu AttributeUsage musi dziedziczyć
        // z klasy Attribute
        protected string description;
        public HelpAttribute(string Description)
        {
            // this mówi nam, że odwołujemy się do składowej danej klasy
            // nie jest to wymagane ale pozwoli jednoznacznie określić
            // że chcemy do pola naszej klasy przypisać wartość z konstruktora
            this.description = Description;
        }
    }
    [Help("Ta klasa nic nie robi")]
    class Przyklad1
    {
        public Przyklad1()
        {
        }
        // Atrybut ten nie może zostać użyty w tej metodzie gdyż zostało to 
        // zabronione przy definicji atrybutu AttributeUsage
        [Help("Ta metoda nic nie robi")]
        public void Metoda1()
        {
            // I teraz bardzo ważna uwaga dla czytelników
            // Atrybuty są ściśle związane z mechanizmem refleksji
            // Mechanizm ten pozwala na uzyskiwanie informacji o typie w trakcie kompilacji
            // Możemy zatem uzyskać informacje o atrybutach
            // Na tym jednak skupimy się w kolejnym rozdziale
        }
    }
}

Conditional

Atrybut ten oznacza metodę warunkową, której wykonanie będzie zależało od zdefiniowanego identyfikatora.

Powoduje warunkową kompilację wywołań metod w zależności od podania wartości takich jak: Debug czy Trace. Dzięki takiej kompilacji warunkowej możemy wyświetlać dodatkowe informacje w trakcie debugowania naszego kodu.

Składnia definicji atrybutu:
[Conditional(
   symbol_warunkowy
)]
Przykład użycia atrybutu:
#define DEBUG
using System;
using System.Diagnostics;
namespace AtrybutyConditional
{
    class Program
    {
        static void Main(string[] args)
        {
            DisplayMessage.Message("Wewnątrz głównej metody");
            method1();
            Console.ReadKey();
            // Wynik działania programu
            //Wewnatrz glównej metody
            //Wewnatrz metody 1
            //Wewnatrz metody 2
        }
        static void method1()
        {
            DisplayMessage.Message("Wewnątrz metody 1");
            method2();
        }
        static void method2()
        {
            DisplayMessage.Message("Wewnątrz metody 2");
        }
    }
    class DisplayMessage
    {
        [Conditional("DEBUG")]
        public static void Message(string message)
        {
            Console.WriteLine(message);
        }
    }
}

Obsolete

Atrybut ten oznacza część programu, która nie powinna być dłużej używana. Prosty przykład dotyczy powstania nowej metody przy konieczności pozostawienia starej w naszym kodzie. Tą drugą możemy oznaczyć jako przestarzałą (Obsolete) przez wyświetlenie komunikatu informującego o tym, że nowa metoda powinna być używana.

Składnia definicji atrybutu:

[Obsolete(
   informacja
)]
[Obsolete(
   informacja,
   czy_traktowac_uzycie_jako_blad	
)]

gdzie,

  • parametr informacja opisuje czemu dana metoda jest przestarzała i co powinno być używane zamiast niej;
  • parametr czy_traktowac_uzycie_jako_blad jest wartością logiczną. Jeżeli wartość logiczna jest prawdziwa kompilator powinien traktować używanie tej metody jako błąd. Domyślne zachowanie kompilatora polega na wygenerowaniu ostrzeżenia – warunek logiczny jest fałszywy.

Przykład użycia atrybutu:
using System;
namespace AtrybutyObsolete
{
    class Program
    {
        static void Main(string[] args)
        {
            // Taki program się nie kompiluje
            // Otrzymujemy poniższy błąd:
            // 'Program.OldMethod()' is obsolete: 'Proszę nie używać tej metody, nowa wersja: NewMethod()'
            OldMethod();
        }
        [Obsolete("Proszę nie używać tej metody, nowa wersja: NewMethod()", true)]
        static void OldMethod()
        {
            Console.WriteLine("Stara, nieużywana metoda");
        }
        static void NewMehtod()
        {
            Console.WriteLine("Nowa wersja metody");
        }
    }
}

Tworzenie niestandardowych atrybutów

.NET Framework pozwala na tworzenie niestandardowych atrybutów, które mogą być używane do przechowywania różnych informacji i pobrane w trakcie wykonywania programu. Informacje te mogą być związane z jakimkolwiek elementem w zależności od kryterium projektowego i potrzeb aplikacji.

Tworzenie i używanie niestandardowych atrybutów związane jest z czterema poniższymi krokami:

  • deklaracja atrybutu niestandardowego;
  • konstruowanie atrybutu niestandardowego;
  • użycie atrybutu na docelowym elemencie programu;
  • dostęp do atrybutu przez mechanizm refleksji.


Deklaracja atrybutu niestandardowego

Nowy niestandardowy atrybut powinien dziedziczyć z klasy System.Attribute:

[AttributeUsage(AttributeTargets.Class |
    AttributeTargets.Constructor |
    AttributeTargets.Method |
    AttributeTargets.Field |
    AttributeTargets.Property, 
    AllowMultiple = true)]
public class DebugInfo : Attribute
{
}

W powyższym przykładzie zdefiniowaliśmy atrybut o nazwie DebugInfo.


Konstruowanie atrybutu niestandardowego

Przygotujmy atrybut niestandardowy, który będzie przechowywał informacje o naszym programie. Będzie on przechowywał następujące informacje:

  • numer kodu błędu;
  • imię osoby, która zidentyfikowała błąd;
  • datę ostatniego sprawdzania kodu;
  • informacja tekstowa zawierające komentarz programisty.

Nasz atrybut DebugInfo będzie posiadał trzy prywatne właściwości do przechowywania trzech pierwszych informacji oraz właściwość publiczną do przechowywania komentarza programisty. Stąd, numer błędu, imię programisty oraz data przeglądu kodu są parametrami klasy DebugInfo a komentarz jest opcjonalny.

public class DebugInfo : Attribute
{
    // pola prywatne
    private int codeNumber;
    private string developerName;
    private string lastReviewData;
    public string message;
    public DebugInfo(int code, string dev, string d)
    {
        this.codeNumber = code;
        this.developerName = dev;
        this.lastReviewData = d;
    }
    // właściwości, które dają nam dostęp do pól prywatnych
    // ich użycie będzie potrzebne podczas korzystania z mechanizmu refleksji
    // ale o tym w kolejnym rozdziale
    public int CodeNumber
    {
        get
        {
            return codeNumber;
        }
    }
    public string DeveloperName
    {
        get
        {
            return developerName;
        }
    }
    public string LastReviewData
    {
        get
        {
            return lastReviewData;
        }
    }
}

Użycie atrybutu

Atrybutu używa się poprzez użycie go bezpośrednio nad elementem docelowym:

[DebugInfo(23, "Paweł", "27/11/2015", message = "Zły zwracany typ")]
[DebugInfo(223, "Paweł", "29/11/2015", message = "Nieprzypisana wartość")]
class Rectangle
{
    protected double length;
    protected double width;
    public Rectangle(double l, double w)
    {
        length = l;
        width = w;
    }
    [DebugInfo(11, "Paweł", "22/11/2015", message = "Zły zwracany typ")]
    public double GetArea()
    {
        return length * width;
    }
}