Poznáváme C# a Microsoft.NET 38. díl – atributy a jejich reflexe

Tento díl bude věnován seznámení s možností definice deklarativních informací k jednotlivým elementům aplikace, jež se v prostředí .NET frameworku provádí užitím atributů. Také se naučíme vytvářet naše vlastní atributy a pomocí reflexe je i získávat.

Proč atributy?

V každém programovacím jazyce, pomocí specifických konstrukcí, které daný jazyk nabízí, deklarujeme u jednotlivých elementů programového kódu řadu informací. Například pokud vytváříme třídu, můžeme uvést, která všechna rozhraní jsou touto třídou implementována nebo u jednotlivých členů našeho typu specifikujeme modifikátory přístupu pro určení jejich viditelnosti. Omezením tohoto přístupu je fakt, že takto zadávané informace k elementům kódu jsou omezeny na použití předem určených konstrukcí konkrétního jazyka.

V prostředí .NET frameworku je možné pro zadání dodatečných informací k elementu programu použít takzvané atributy. Atributy jsou tedy jazykovými konstrukcemi, které mohou doplnit elementy programového kódu (assembly, moduly, typy, členy, návratové hodnoty a parametry) o specifické doplňující informace.

Jak atributy použít?

Stejně jako vše ostatní, tak i atributy jsou v prostředí .NET frameworku objekty. Každý použitý atribut je instancí třídy, která buď přímo či nepřímo dědí od třídy System.Attribute.

V jazyce C# jsou atributy specifikovány uvedením jména atributu do hranatých závorek nad konkrétním elementem programového kódu. V uvedeném příkladu je použití atributů demonstrováno na označení třídy za zastaralou pomocí atributu Obsolete.

/// <summary>
/// Ukazka pouziti atributu
/// </summary>
 
[Obsolete("Tato trida by jiz nemela byt dale vyuzivana",false)]
public class StaraTrida
{
  //nejaka implementace tridy 
}

Podle konvence by měl název každé třídy představující třídu atributu končit slovem Attribute , aby třída byla odlišena od ostatních tříd. Ale při použití jednotlivých atributů nejsme nuceni specifikovat celý název atributu. Nemusíme tedy všude uvádět onu příponu Attribute, což jste vlastně mohli vidět na předchozím příkladu s použitím třídy atributu, jejíž celý název je ve skutečnosti ObsoleteAttribute. To znamená, že použití atributu v předchozím příkladu bylo ve výsledku shodné s tímto použitím:

[System.ObsoleteAttribute("Tato trida by jiz nemela byt dale vyuzivana",false)]
public class StaraTrida
{
  //nejaka implementace tridy 
}

Samozřejmě lze k elementu přiřadit i více než jeden atribut a to tak ,že jednotlivé atributy od sebe oddělíme čárkami.

[Obsolete(),Serializable()]
public class TridaViceAtributu
{
  //nejaka implementace tridy 
}

Poziční a pojmenované parametry atributů

Atributy mohou přebírat parametry, které rozšiřují množství doplňujících informací při jejich použití. Při používání atributů se používají dva typy předávaných parametrů. Prvním z nich jsou parametry poziční, které odpovídají parametrům předávaným veřejným konstruktorům typu daného atributu. Naproti tomu, použití parametrů pojmenovaných představuje cestu, nastavení veřejných datových členů nebo vlastností typu daného atributu.

Pojmenované parametry se narozdíl od pozičních paremetrů, musejí specifikovat zadaním jména datového členu nebo vlastnosti, kterou nastavují. V následujícím příkladu je použit atribut DllImport sloužící k použítí metod v DLL knihovnách napsaných v neřízeném jazyce. Pozičním parametrem atributu předáváme název knihovny (“user32.dll”) a pomocí pojmenovaného parametru CharSet sdělujeme jaká znaková sada má být použita pro řetězce.

public class NejakaTrida
{
[System.Runtime.InteropServices.DllImport("user32.dll",CharSet=System.Runtime.InteropServices.CharSet.Ansi)]
public static extern void NejakaLinkovanaMetoda();
}

Cílené použití atributů

V některých situacích může při použití atributu docházet k nejednoznačnosti. Například použijeme-li atribut pro metodu, může to znamenat, že atribut náleží k metodě nebo k její návratové hodnotě.

class NejednoznacnePouziti
{
[NejakyAttribut()]
  public int NejakaMetoda()
  {
    return 0;
  }
}

I přes tuto nejednoznačnost půjde takovýto kód zkompilovat, protože C# má pro každý možný typ nejednoznačné deklarace atributu určený výchozí element pro který má atribut použít. V tomto konkrétním příkladu by to byla právě metoda. Pokud chcete vědět všechny výchozí elementy pří nejednoznačných deklaracích, odkážu vás na přehlednou tabulku uvedenou v .NET framework SDK.

Pokud nechceme, aby při takovéto deklaraci použití atributu byl atribut použit pro výchozí element, můžeme ono výchozí použití překrýt a to právě použitím cíleného použití atributu. Obecně se cílené použití atributu zapisuje takto :

[cil : seznam_atributů]

Cíl může být jeden z těchto : assembly, field, event, method, module, param, property, return, type.

class CilenePouziti
{
  [return: NejakyAttribut()]
  public int NejakaMetoda()
  {
    return 0;
  }

}

Poznámka: Využití atributů aplikovaných pro návratové hodnoty metod nalezneme především při interakci s neřízeným kódem.

Vytváření vlastních atributů

Vytvářet vlastní atributy je velice snadné. Jediné co nám k vytvoření atributu stačí je vytvoření třídy, která přímo nebo nepřímo dědí ze střídy System.Attribute. Takže pokud se rozhodneme, že vytvoříme atribut nesoucí doplňující informace o autorovi nějakého elementu programového kódu, mohla by implementace vypadat nějak takto:

/// <summary>
/// Trida predstavujici uzivatelsky atribut slouzici k definici
/// autora programoveho elementu
/// </summary>
 
//specifikujeme na kterych elementech muze byt atribut pouzit
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct|AttributeTargets.Method)]
public class AuthorAttribute : Attribute
{
  private string jmeno;
  private string prijmeni;
  private double verze;
   
  public AuthorAttribute(string jmeno, string prijmeni)
  {
    this.jmeno = jmeno;
    this.prijmeni = prijmeni;
    this.verze = 1.00;
  }

  public string Jmeno
  {
    get{return jmeno;}
  }

  public string Prijmeni
  {
    get{return prijmeni;}
  }

  //tato vlastnost bude moci byt prirazena pomoci
  //pojmenovaneho parametru
  public double Verze
  {
    get{return verze;}
    set{verze = value;}
  }


}

Při použití atributu bude zadáváno jméno a příjmení autora a to pomocí pozičních parametrů, protože veřejný konstruktor typu tyto parametry očekává. Volitelně bude možné použitím pojmenovaného parametru specifikovat verzi elementu, protože vlastnost Verze je deklarována jako veřejná nastavitelná vlastnost.

Třídě atributu jsme definovali atribut AttributeUsage, jehož použitím specifikujeme na které elementy programového kódu bude možné atribut použít. V našem případě půjde atribut použít na třídy, struktury a metody, což je uvedeno pomocí výčtu AttributeTargets.

Získání uživatelských atributů při použití reflexe

Nyní, když už umíme atributy vytvářet ještě potřebujeme zjistit zda je pro programový element konkrétní atribut definován. Teď když jsme již vybaveni znalostmi mechanismu reflexe si musíme naše znalosti o ní mírně rozvinout a to právě proto, že pomocí reflexe jsme schopni přítomnost atributu na elementu zjistit. V následujícím příkladu je u pár ukázkových tříd použit námi vytvořený atribut AuthorAttribute a použitím reflexe jsou pro jednotlivé třídy zjištěny jejich autoři, případně jsou zjištěny i autoři jednotlivých metod.

/// <summary>
/// Priklad na pouziti atributu AuthorAttribute a pouziti reflexe atributu
/// </summary>
public class AtributyPriklad
{
  static void VypisAutory(Type Typ)
  {
    Console.WriteLine("Zpracovavam typ {0}",Typ.Name);
    //zjistime, zda je pro dany typ definovan atribut AuthorAttribute
    if ( Attribute.IsDefined(Typ,typeof(AuthorAttribute)) )
    {
      //ziskame atribut
      AuthorAttribute lAuthorAttr = (AuthorAttribute) Attribute.GetCustomAttribute(Typ,typeof(AuthorAttribute));
      Console.WriteLine("Autor typu : {0}, Verze : {1}",lAuthorAttr.Prijmeni + lAuthorAttr.Jmeno, lAuthorAttr.Verze);
    }
    //ziskame seznam verejnych instnancnich metod
    MethodInfo[] lMethods = Typ.GetMethods(BindingFlags.Public|BindingFlags.Instance);
    foreach(MethodInfo lMethod in lMethods)
    {
      //pokud je pro metodu definovan atribut typu AuthorAttribute vypiseme info
      if ( Attribute.IsDefined(lMethod, typeof(AuthorAttribute)) )
      {
        AuthorAttribute lMethodAuthor = (AuthorAttribute) Attribute.GetCustomAttribute(lMethod,typeof(AuthorAttribute));
        Console.WriteLine("Autor metody {0} : {1}, Verze : {2}", lMethod.Name, lMethodAuthor.Prijmeni + lMethodAuthor.Jmeno, lMethodAuthor.Verze);
      }
    }
  }

  public static void SpustPriklad()
  {
    VypisAutory(typeof(PrvniTrida));
    VypisAutory(typeof(DruhaTrida));
    VypisAutory(typeof(TretiTrida));
  }
}

[Author("Jiri","Joudek")]
class PrvniTrida
{
  //nejaka implementace
}
[Author("Michal","Racek",Verze=1.2)]
class DruhaTrida
{
  [Author("Michal","Racek",Verze=1.1)]
  public void NejakaMetoda()
  {
    //implementace metody
  }

  //dalsi implementace tridy
}

[Author("Jiri","Joudek",Verze=1.3)]
class TretiTrida
{
  //nejaka implementace
}

Přítomnost deklarace atributu pro konkrétní element zjistíme pomocí statické metody IsDefined třídy Attribute, které v jedné z přetížených verzí předáme instanci potomka třídy MemberInfo představující element u něhož přítomnost atributu zjišťujeme a druhým parametrem je typ atributu který hledáme. Pro získání atributu použijeme další statickou metodu třídy Attribute, kterou je metoda GetCustomAttribute, jíž v našem příkladu předáme stejné parametry jako v případě metody IsDefined. Jelikož metoda GetCustomAttribute vrací odkaz na objekt typu Attribute, tak pokud chceme používat členy definované na třídě AuthorAttribute musíme provést explicitní přetypování.

Zdrojové kódy k příkladům jsou ke stažení zde.

Diskuze (5) Další článek: IDF: fotografie všech dvoujádrových procesorů

Témata článku: , , , , , , , , , , , , , , , , ,