Automaticky generované GUI pomocí reflexe

Seznam článků

Reflexe umožňuje získávat metainformace o třídách, jejich vlastnostech, metodách i událostech a ty pak používat, aniž bychom museli vědět či deklarovat, o jakou konkrétní třídu se vlastně jedná. Při vhodném použití této technologie pak můžeme generovat tabulkové přehledy dat a formuláře pro editaci záznamů univerzálním způsobem tak, že jeden kód dokáže vygenerovat funkční GUI pro instanci libovolné třídy. V informačním systému používajícím tento princip pak není třeba programovat stovky oken, ale pouze vhodně nedeklarovat třídy a propojit je s databází, což víceméně automatizovaně obstará LINQ to SQL. 

Reflexe

Třídy, struktury, výčty apod. většinou deklarujeme a používáme přímo pro konkrétní případy použití. To je samozřejmě v pořádku. Vytvoříme si například třídu Osoba s vlastnostmi jako Jmeno, Prijmeni, DatumNarozeni atd. (viz kód níže), pro načtení hodnot do instancí této třídy, ať již zadáním, ze souboru, z databáze, z internetu..., použijeme konstruktor (var osoba = new Osoba();) a přímý přístup k jejím vlastnostem (např. osoba.Jmeno = "Alois";). 

namespace Reflexe
{
    public class Osoba
    {
        public string Jmeno { get; set; }
        public string Prijmeni { get; set; }
        public DateTime DatumNarozeni { get; set; }
        public int Vyska { get; set; }
    }
}

Pro zobrazení tohoto seznamu pak vytvoříme zvláštní okno (nebo webovou stránku, mobilní obrazovku atd.) s tabulkou (Grid, Table...) či přehledem (ListView, DataList...) a pro zobrazení detailních informací popř. editaci konkrétního záznamu nějaký formulář (form, FormView, DetailView, Grid či StackLayout s editory; záleží na použité technologii), s individuálním editorem pro každou vlastnost. Máme-li nějaký rozsáhlejší systém třeba s dvaceti takovýmito třídami, pak je pro každou z nich obvykle nezbytné tato dvě okna (přehled a detail) navrhnout, vytvořit, odladit a udržovat. To už je nějakých 40 oken (či stránek) + úvodní nabídka s menu popř. nějaká pokročilejší navigace. Co kdyby se ale tato okna vytvářela sama?

Existuje totiž, alespoň tedy v .NET C#, sytém zvaný Reflexe (Reflection), který umožňuje pracovat s metadaty tříd, struktur atd., obecně tedy datových typů (Type). Vše potřebné se nachází ve jmenném prostoru (namespace) System.Reflection. Funguje to jak v klasickém .NET tak i v Xamarin.Forms, tedy u multiplatformních aplikací (UWP, Android, iOS), byť s drobnými odlišnostmi v syntaxi (v XF třída Type nemá všechny potřebné atributy, ty jsou skryty až ve třídě TypeInfo). Tyto rozdíly shrnuje následující tabulka.

Popis.NETXamarin.Forms
Typ
Název třídy s informacemi (metadaty) o třídě, struktuře atd. Type TypeInfo
Získání Type/TypeInfo z objektu (instance třídy) Type t = osoba.GetType(); TypeInfo t = osoba.GetType().GetTypeInfo();
Získání Type/TypeInfo ze třídy Type t = typeof(Osoba); TypeInfo t = typeof(Osoba).GetTypeinfo();
Konstruktor
Vytvoření instance třídy z Type/TypeInfo (přes bezparametrický konstruktor) var c = GetConstructor(new Type[]{});
var osoba = c.Invoke(null);
var osoba = new Activator.CreateInstance(t);

Další tabulka už pak ukazuje jednotný kód, protože je pro .NET i Xamarin.Forms shodný. Rozdíl je pouze v získání typu t (viz předchozí tabulka), který je buď Type v .NET nebo TypeInfo v Xamarin.Forms. 

PopisKód
Typ
Název typu string nazev = t.Name;
Celý název typu (včetně namespace) string nazev = t.FullName;
Vlastnosti
PropertyInfo konkrétní vlastnosti daného názvu var prop = t.GetProperty("Jmeno");
Název vlastnosti string nazev = prop.Name;
Datový typ vlastnosti Type tp = prop.PropertyType;
Je datový typ vlastnosti string? bool isString = tp == typeof(string);
Je vlastnost pro čtení (má get kód),
a pro zápis (má set kód)?
bool hasGet = prop.CanRead;
bool hasSet = prop.CanWrite;
Získání hodnoty vlastnosti daného objektu var jmeno = prop.GetValue(osoba);
Nastavení hodnoty vlastnosti danému objektu prop.SetValue(osoba, "Alois");
Seznam vlastností (PropertyInfo) třídy včetně vlastností předků var props = t.GetProperties();
Seznam pouze veřejných (public) vlastností var props = t.GetProperties(BindingFlags.Public);
Metody
MethodInfo konkrétní nepřetížené metody daného názvu var meth = t.GetMethod("GetJmeno");
MethodInfo konkrétní přetížené metody daného názvu a typů vstupních parametrů var meth2 = t.GetMethod("SetJmeno", new Type[] {typeof(string)});
Typ návratové hodnoty metody Type rt = meth.ReturnType;
Spuštění metody: bezparametrické,
se získáním návratové hodnoty,
se vstupními parametry
meth.Invoke(this, new object[] {});
var result = meth.Invoke(this, new object[] {});
meth2.Invoke(this, new object[] {"Alois"});
Assembly
Získání Assembly (informací o celém projektu) Assembly a = t.Assembly;
Získání Assembly hlavního spouštěncího (executing/Startup) projektu Assembly a = Assembly.GetExecutingAssembly();
Všechny typy v projektu Type[] types = a.GetTypes();
Typ daného názvu (celého názvu, včetně namespace "Reflexe") Type t = a.GetType("Reflexe.Osoba");
Typ daného názvu (jen názvu třídy, bez namespace; název by se neměl v projektu opakovat, nebo by to mohlo vrátit typ jiné třídy než očekáváme) Type t = a.GetTypes().FirstOrDefault(x => x.Name == "Osoba");
Atributy
Všechny vlastní atributy člena třídy či třídy samotné (mi = prop/meth/t/a/...; mi - MemberInfo je předek PropertyInfo, MethodInfo, Type, Assembly...) IEnumerable<CustomAttributeData> atrs = mi.CustomAttributes;
Všechny vlastní atributy člena daného typu (třídy) T IEnumerable<T> atrs = mi.GetCustomAttributes<T>();
Vlastní atribut člena daného typu (třídy) T, je-li jen jeden T atr = mi.GetCustomAttribute<T>();

 

on 27 květen 2018