Poznáváme C# a Microsoft.NET 37. díl – použití reflexe

Po minulém úvodu do světa reflexe se v tomto díle budeme věnovat vybraným reflektujícím typům. Dozvíme se jak je například možné pomocí reflexe za běhu dynamicky vyhledávat a vykonávat metody nebo jakým způsobem za běhu vytvoříme instanci třídy na základě jejích metadat.

Třída System.Type

Instance této představují abstrakci deklarace určitého typu ať už se jedná o třídu nebo strukturu, výčet (enum) či rozhraní. Tato třída je základní cestou k metadatům typu v mechanismu reflexe. Instanci této třídy lze získat několika způsoby, základní tři byly zmíněny v minulém díle (Type.GetType ve svých dvou verzích a operátor typeof). Pomocí instančních vlastností a metod této třídy můžeme zjišťovat rozličné informace o daném typu, včetně seznamu jeho členů, které mohou být jak víme datové členy, vlastnosti, metody, konstruktory a události.

Na ukázku použití je zde následující příklad, který zjistí některé informace o typu Osoba, který byl použit již v minulém díle (jediná změna je, že byla do třídy přidána definice metody ToString, která při svém zavolání vrátí jméno a příjmení dané osoby) Zdrojový kód třídy Osoba nebudu v článku uvádět, můžete jej v případě potřeby nalézt v přiložených zdrojových kódech.

/// <summary>
/// Priklad na zjisteni informaci z metadat pomoci tridy Type
/// </summary>
public class TypePriklad
{
  /// <summary>
  /// Vypise par informaci z metadat
  /// </summary>
  public static void VypisInfo(Type Typ)
  {
    Console.WriteLine("Kvalifikovany nazev typu : {0} ",Typ.FullName);
    Console.WriteLine("Kvalifikovany nazev assembly do niz spada : {0}",Typ.AssemblyQualifiedName);
    Console.WriteLine("Nazev predka : {0}",Typ.BaseType.FullName);
    Console.WriteLine("Je hodnotovy typ : {0}",Typ.IsValueType);
    Console.WriteLine("Trida je zapecetena : {0}",Typ.IsSealed);
    Console.WriteLine("Trida je verejna : {0}",Typ.IsPublic);
  }

  public static void Priklad()
  {
    //ziskame assembly s nazvem PrikladyZive37
    Assembly lZiveAssembly = Assembly.Load("PrikladyZive37");
    //ziskame vsechny typy, ktere se v ni nachazeji
    Type lOsobaType = lZiveAssembly.GetType("PrikladyZive37.Osoba");
    VypisInfo(lOsobaType);
  }
}

Na přikladu můžete vidět další způsob získání instance třídy Type a to použitím metody GetType na instanci třídy Assembly, kterou jsme získali zavoláním metody Load, jíž jsme předali název assembly, kterou chceme reflektovat. Následně je získaný typ předán metodě VypisInfo, který vypíše pár informací o něm.

Dynamické vytvoření instance třídy

Občas můžeme chtít vytvořit instanci třídy k níž máme k dispozici informace právě v podobě instance třídy Type. Jedna z cest jak tohoto dosáhnout je využít třídu Activator ze jmenného prostoru System. Jeho metoda CreateInstance v jedné ze svých mnoha přetížených verzí očekává ve formě parametru instanci třídy Type. V tomto příkladu je třída Activator použita k vytvoření instance třídy za běhu programu.

/// <summary>
/// Ukazka pouziti tridy Activator pro dynamicke vytvoreni instance tridy
/// </summary>
public class ActivatorPriklad
{
  public static void CreateInstancePriklad()
  {
    //ziskame pole instanci tridy Assembly, ktere nalezi do aplikacni domeny
    Assembly[] lAssemblies = AppDomain.CurrentDomain.GetAssemblies();
    foreach( Assembly lAssembly in lAssemblies)
    {
      //ziskame vsechny typy z assembly
      Type[] lTypes = lAssembly.GetTypes();
      Console.WriteLine("Prohledavam assembly {0}", lAssembly.FullName);
        foreach(Type lType in lTypes)
        {
          //pokud se jedna o instanci tridy Type predstavujici typ tridy
          //osoba, tak vytvorime jeji instanci
          if ( lType.Name.Equals("Osoba") )
          {
            Console.WriteLine("Nalezena metadata pro tridu Osoba");
            Osoba lInstance = (Osoba) Activator.CreateInstance(lType);
            //nyni muzeme provadet libovolne operace s istanci tridy osoba
            lInstance.Jmeno = "Jan";
            lInstance.Prijmeni = "Novak";
            Console.WriteLine(lInstance.ToString());
          }
        }
    }
  }
}

Kromě použití třídy Activator jsem chtěl v tomto příkladu uvést i to jak je možné získat seznam assemblies (metoda GetAssemblies na instanci AppDomain) z aplikační domény a také jak získat informace o všech typech v konkrétní assembly (metoda GetTypes na instanci Assembly). V příkladu je procházeno polem instancí třídy Type z každé assembly nahrané v aplikační doméně a pokud je jednoduchý název typu shodný s názvem třídy Osoba, tak je dynamicky vytvořena její instance. V tomto konkrétním příkladu bude seznam získaných assembly obsahovat pouze dvě instance třídy Assembly. První z nich bude představovat informace o assembly mscorlib.dll, což je assembly .NET frameworku samotného a druhá bude představovat assembly naši tj. PrikladyZive37.dll.

Poznámka: Možná teď někteří z vás kroutí hlavou a diví se, že jsem pro získání instance třídy Type popisující typ Osoba nepoužil jednoduše Type.GetType, ale jak jsem psal, tak v tomto příkladu jsem chtěl také demonstrovat možné získání seznamů aplikačních elementů.

Reflexe datových členů

Reflektujícím typem datových členů třídy nebo struktury je třída FieldInfo. Jejím využitím je například možné zjistit modifikátor přístupu daného datového členu a snadné je i zjištění jeho datového typu. To nám ukazuje první příklad použití tohoto typu.

public static void VypisInfo()
{
  Type lOsobaType = typeof(Osoba);
  //ziskani vsech instancnich dat. clenu
  FieldInfo[] lFields = lOsobaType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  foreach(FieldInfo lField in lFields)
  {
    Console.WriteLine("Nazev clenu : {0}",lField.Name);
    Console.WriteLine("Typ datoveho clenu : {0}", lField.FieldType);
    Console.WriteLine("Clen je privatni : {0}" ,lField.IsPrivate);
    Console.WriteLine("Clen je verejny : {0}", lField.IsPublic);
    Console.WriteLine("Clen je staticky : {0}", lField.IsStatic);
  }
}

Při získávání informací o datových členech typu si musíme dát pozor na použití příznaků BindingFlags, které se používají k určení výběru členů a to nejen v metodě GetFields, ale obecně u všech metod, které z metadat získávají instance potomků třídy MemberInfo. U reflexe datových členů je to však o to důležitější neboť pokud tyto příznaky neuvedeme korektně, tak námi získané pole bude prázdné.

Druhý příklad použití třídy FieldInfo je zde pro ukázku toho, jak je možné pomocí reflexe nastavit hodnotu datového členu.

public static void NastaveniHodnotyPriklad()
{
  //vytvoreni instance
  Osoba lOsoba = new Osoba();
  lOsoba.Jmeno = "Jan";
  Console.WriteLine("Jmeno osoby : {0}", lOsoba.Jmeno);
  Type lOsobaType = typeof(Osoba);
  //ziskani datoveho clenu jmeno
  FieldInfo lJmenoField = lOsobaType.GetField("jmeno",BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
  //nastaveni datoveho clenu jmena instance lOsoba
  lJmenoField.SetValue(lOsoba,"Karel");
  Console.WriteLine("Jmeno osoby : {0}", lOsoba.Jmeno);
}

Pro přiřazení hodnoty datového členu konkrétní instance je potřeba použít metody SetValue, jíž předáme instanci, na které chceme hodnotu členu pro něž máme získanou instanci třídy FieldInfo nastavit a samozřejmě kýženou hodnotu. Zajímavé na použití reflektivního přiřazování hodnot je fakt, že hodnotu lze nastavit nezávisle na modifikátoru přístupu datového členu, takže i když jsou všechny datové členy definované na třídě Osoba soukromé, je možné jim bez problému nastavit hodnotu. Reflexe je holt mocná zbraň. Obdobně jako se pracuje s instancemi třídy FieldInfo se pracuje i s instancemi třídy PropertyInfo, které slouží k reflexi přístupových vlastností.

Reflexe metod

K reflexi metod definovaných na jednotlivých typech je pro nás v rámci .NET připravena třída MethodInfo. Stejně jako u ostatních reflektujících typů je možné o dané metodě zjistit mnoho zajímavých informací, jako je její návratový typ, zda-li je virtuální atd.

public static void VypisInfo()
{
  Type lOsobaType = typeof(Osoba);
  MethodInfo[] lMethods = lOsobaType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
  foreach(MethodInfo lMethod in lMethods)
  {
    Console.WriteLine("Nalezena informace o metode {0}", lMethod.Name);
    Console.WriteLine("Metoda je virtualni : {0}", lMethod.IsVirtual);
    Console.WriteLine("Metoda je staticka : {0}", lMethod.IsStatic);
    Console.WriteLine("Navratovy typ metody : {0}", lMethod.ReturnType);
    Console.WriteLine("Metoda je verejna : {0}",lMethod.IsPublic);
}
}

Jak můžeme vidět, tak pro získání metod stačí na instanci třídy Type zavolat metodu GetMethods, popřípadě specifikovat hledané metody pomocí výčtového příznaku BindingFlags. Přetížená verze této metody bez parametru vyhledá pouze veřejné instanční metody.

Samozřejmě je kromě pouhého zjišťování informací o metodě i danou metodu zavolat. Za tímto účelem je na třídě MethodBase (společný předek tříd MethodInfo a ConstructorInfo) definována metoda Invoke, jejíž použití ukazuje následující příklad.

public static void VyvolaniPriklad()
{
  Type lOsobaType = typeof(Osoba);
  //ziskani metody ToString
  MethodInfo lToStringMethod = lOsobaType.GetMethod("ToString");
  //vytvoreni instance pomoci reflexe
  Osoba lInstance = (Osoba) Activator.CreateInstance(lOsobaType);
  lInstance.Jmeno = "Jan";
  lInstance.Prijmeni = "Novak";
  Console.WriteLine("Vyvolavam metodu ToString..");
  //vyvolani metody na instanci
  object lResult = lToStringMethod.Invoke(lInstance,null);
  //vypsani vysledku volani metody
  Console.WriteLine(lResult);
}

Instancí třídy MethodInfo reflektující metodu ToString jsme získali použitím metody GetMethod, které jsme předali název požadované metody. Prvním parametrem metody Invoke je objekt instance na kterém má být konkrétní metoda zavolána a druhým je pole objektů typu System.Object představující formální parametry metody (v tomto případě jsme metodě žádné parametry nepředávali). Jak by volání metody s parametry pomocí reflexe vypadalo? To se snaží ukázat příklad, který následuje.

public static void VyvolaniParametryPriklad()
{
  Type lStringType = typeof(string);
  //vytvoreni parametru pro konstruktor
  char[] lParam = new char[4]{`T`,`e`,`x`,`t`};
  //vytvoreni instance pomoci reflexe
  string lInstance = (string) Activator.CreateInstance(lStringType,new object[1]{lParam});
  //vytoreni sady parametru pro metodu, aby nedoslo k nejednoznacnosti
  Type[] lMethodParams = new Type[1];
  lMethodParams [0] = typeof(string);
    //ziskani metody IndexOf ve verzi prijimajici jeden parametr typu string
  MethodInfo lIndexOfMethod = lStringType.GetMethod("IndexOf",lMethodParams);
  object lResult = lIndexOfMethod.Invoke(lInstance,new object[1]{"x"});
  Console.WriteLine(lResult);
}

Jelikož je metoda IndexOf, která je definována na třídě String přetížená musíme přesně zvolit tu verzi, která má být reflektována, jinak by při jejím získávání pomocí metody GetMethod došlo k nejednoznačnosti a byla by vyvolána výjimka AmbiguousMatchException. Jak se takovéto situaci vyhnout? Odpověď na tuto otázku přichází v podobě použití přetížené verze metody GetMethod a to verze, která kromě názvu metody přijímá ještě druhý parametr a tím je pole objektů typu Type. V podobě tohoto pole totiž řekneme jaké parametry má hledaná metoda přijímat.

V našem případě je toto pole pouze jednoprvkové a obsahuje pouze instanci třídy Type pro třídu String. Tím říkáme, že chceme získat verzi metody očekávající právě jeden parametr a ten má být typu String. Také konstruktor třídy String je přetížený a to se projevilo při dynamickém vytváření instnance pomocí třídy Activator, kde musíme zadat ve formě pole objekty představující parametry konstruktoru (zde konkrétně voláme verzi konstruktoru s parametrem typu pole znaků).

Soubory se zdrojovými kódy k příkladům naleznete zde.

Diskuze (4) Další článek: Spuštěn blog o Jyxo

Témata článku: Software, Microsoft, Programování, Mocná zbraň, Public, Microsoft NET, Díl, Metadata, Reflex, Druhý příklad, Použití, Společný předek, Jediná změna, Zavolání, LTO


Určitě si přečtěte

WindowsFX: Nainstalujte to mamce a taťkovi. Ani nepoznají, že to je Linux

WindowsFX: Nainstalujte to mamce a taťkovi. Ani nepoznají, že to je Linux

** Po dvou měsících tu máme další linuxovou kopii ** Tentokrát jde o imitaci Desítek ** Sestavili ji brazilští geekové nad Ubuntu

Jakub Čížek | 144

Filmové pirátství asi jen tak nezmizí. Když už musíte, stahujte bezpečně v Seedru

Filmové pirátství asi jen tak nezmizí. Když už musíte, stahujte bezpečně v Seedru

** Máme HBO Go, máme Netflix... ** Ale stejně krademe filmy a seriály ** Když už musíte, stahujte torrenty bezpečně v Seedru

Jakub Čížek | 141

Zapomeňte na kometu, české nebe každý den křižují mnohem zajímavější kousky

Zapomeňte na kometu, české nebe každý den křižují mnohem zajímavější kousky

** České nebe každý den křižuje hromada exotických letounů ** Na populární mapě Flightradar24 je ale nenajdete ** Jsou to vojenské letouny USA, UK a NATO

Jakub Čížek | 39

10 věcí, které nás štvou na Windows 10 a bohužel asi jen tak nepřestanou

10 věcí, které nás štvou na Windows 10 a bohužel asi jen tak nepřestanou

** Windows 10 je na trhu 5 let, ale pořád má velké rezervy ** Ani desátá velká aktualizace, která vyjde na podzim, je nevyřeší ** Štvou nás Windows Update, Store, Nastavení atd.

Lukáš Václavík | 146


Aktuální číslo časopisu Computer

Megatest mobilů do 8 000 Kč

Test bezdrátových headsetů

Linux i pro začátečníky

Jak surfovat anonymně