Emberek esetén fontos és hasznos képesség az önismeret, mert csak ennek birtokában tudunk kritikát gyakorolni magunkkal szemben és elengedhetetlen ahhoz, hogy jobbá váljunk bármiben is.
Ha a programjainkat is fel tudnánk ruházni az önismeret képességével, akkor olyan kódokat alkothatnánk, amelyek bővítése csak minimális, vagy még jobb esetben semmilyen módosítással nem járna. Ez a gondolat igen futurisztikusan hangzik elsőre, de képzeljük el, hogy írnunk kell egy alkalmazást, ami parancssori programok futtatására képes. A programunknak bővíthetőnek, az egyes parancsokat értelmező és végrehajtó kódoknak viszont beágyazottan elérhetőnek kell lenniük.
A problémának többféleképpen nekiláthatunk. Az eddigiek tanultak, ismertetettek alapján például létrehozhatnánk egy interfészt vagy absztrakt osztályt, ami definiál egy modult, majd ezek alapján egy monumentális switch-case sorozatban eldöntenénk, hogy melyiket is példányosítjuk és futtatjuk le. Ennek egy továbbfejlesztett verziója lehetne, ha a moduljainkat egy generikus listába tennénk és név alapján keresnénk meg a megfelelő objektum futtatandó metódusát.
Azonban mindkét esetben hozzá kell nyúlnunk egy, már létező kódrészlethez. Itt jön képbe a reflection.
A reflection a .NET keretrendszer azon szolgáltatása, melynek segítségével futásidőben férhetünk hozzá egy adott .NET assembly (szerelvény) által definiált típusokhoz, kódrészletekhez. Ezekről információkat kérdezhetünk le, amik alapján az egyes típusokat akár példányosíthatjuk is.
A reflection technológia alapja a Type típus. Egy osztály típusinformációit a typeof() operátorral tudjuk megszerezni. Futásidőben minden objektumnak van egy GetType() metódusa, amit meghívva az aktuális objektum Type példányát kapjuk meg.
Nézzünk egy példát a reflection használatára. Ehhez szükségünk lesz egy típusra, amiről majd információkat szeretnénk szerezni. Legyen ez a típus a Class1 nevű osztály.
namespace PeldaReflection
{
public class Class1
{
public const int Konstans = 42;
public int Tulajdonsag { get; set; }
public Class1()
{
Tulajdonsag = 33;
PrivateTulajdonsag = 44;
}
public void Metodus()
{
Tulajdonsag++;
}
public int Metodus2(int x)
{
return Tulajdonsag % x;
}
private int PrivateTulajdonsag { get; set; }
}
}
A reflection-t megvalósító kód:
using System;
using System.Reflection;
namespace PeldaReflection
{
class Program
{
private static void ListMembers(MemberInfo[] members)
{
foreach(var member in members)
{
//tag neve
Console.WriteLine("{0}", member.Name);
//tag típusa
Console.WriteLine("{0}", member.MemberType);
Console.WriteLine();
}
}
static void Main(string[] args)
{
Type class1Type = typeof(Class1);
MemberInfo[] members = class1Type.GetMembers();
ListMembers(members);
MemberInfo[] field = class1Type.GetMember("PrivateTulajdonsag", BindingFlags.NonPublic | BindingFlags.Instance);
ListMembers(field);
Console.ReadKey();
}
}
}
A program kimenete:
get_Tulajdonsag
Method
set_Tulajdonsag
Method
Metodus
Method
Metodus2
Method
Equals
Method
GetHashCode
Method
GetType
Method
ToString
Method
.ctor
Constructor
Tulajdonsag
Property
Konstans
Field
PrivateTulajdonsag
Property
A fenti példában először lekérdezzük a Class1 típusinformációit, majd a GetMembers metódussal megkapjuk annak összes tagját (például field-eket, property-ket, stb…) egy MemberInfo típusú tömbben. Így a tagok nevéhez, illetve azok típusához férhetünk hozzá.
A tagok lekérése ebben a formában tiszteletben tartja a láthatósági módosítókat, vagyis a private és protected tagokat nem fogjuk látni. Azonban reflection esetén lehetőség van a privát és protected tagok elérésére is. Azonban itt megjegyezném, hogy attól, még hogy lehet, nem biztos, hogy jó ötlet.
Egy privát tag információinak lekérését mutatja be a PrivateTulajdonsag lekérdezése. A GetMember metódus megadott név alapján keres tagokat. Ez is egy tömböt ad vissza a találatokkal, mert név alapján, köszönhetően a polimorfizmusnak, nem garantálható, hogy csak egy találat lesz. A második paraméterben a BindingFlags enum értékei a keresést befolyásolják. Ez egy flag alapú enum, ezért több érték is megadható a bináris "vagy" (|) operátorral összekötve. A BindingFlags.NonPublic | BindingFlags.Instance kombináció beszédes módon azt határozza meg, hogy nem publikus és a példányhoz kötődő tagokra vagyunk csak kíváncsiak.
Ez a példa elég alap szinten mutatta be a reflection képességeit. A reflection számos helyen használt. Eddig csak a Visual Studio IntelliSense szolgáltatása kapcsán találkozhattunk vele. Ennek segítségével moduláris alkalmazásokat tudunk építeni és sok egyéb érdekességet, de ezekhez további osztályokat és metódusokat kell megismernünk.
Reflection API dióhéjban
A Type típus számos Get szóval kezdődő metódust definiál, amivel egy specializált típusban az adott típusú tagról információkat tudunk kapni. A fontosabbak:
Konstruktor információk:
ConstructorInfo[] GetConstructors();
ConstructorInfo[] GetConstructors(BindingFlags bindingAttr);
Adattag információk:
FieldInfo GetField(string name);
FieldInfo GetField(string name, BindingFlags bindingAttr);
FieldInfo[] GetFields();
FieldInfo[] GetFields(BindingFlags bindingAttr);
Esemény információk:
EventInfo GetEvent(string name);
EventInfo GetEvent(string name, BindingFlags bindingAttr);
EventInfo[] GetEvents();
EventInfo[] GetEvents(BindingFlags bindingAttr);
Metódus információk:
MethodInfo GetMethod(string name, Type[] types, ParameterModifier[] modifiers);
MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers);
MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers);
MethodInfo GetMethod(string name, Type[] types, ParameterModifier[] modifiers);
MethodInfo[] GetMethods(BindingFlags bindingAttr);
MethodInfo[] GetMethods();
Tulajdonság információk:
PropertyInfo GetProperty(string name);
PropertyInfo GetProperty(string name, BindingFlags bindingAttr);
PropertyInfo GetProperty(string name, Type returnType);
PropertyInfo GetProperty(string name, Type[] types);
PropertyInfo GetProperty(string name, Type returnType, Type[] types);
PropertyInfo GetProperty(string name, Type returnType, Type[] types, ParameterModifier[] modifiers);
PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers);
PropertyInfo[] GetProperties();
PropertyInfo[] GetProperties(BindingFlags bindingAttr);
Amennyiben a típusunk felsorolás (enum), akkor az alábbi két metódust alkalmazhatjuk a nevek és értékek lekérdezésére:
Array GetEnumValues ();
string[] GetEnumNames ();
A könyv további részében szó lesz még a reflection használatáról, azonban minden osztálya és metódusa nem lesz ismertetve, mert ez CLR (Common Language Runtime) verziónként változhat, illetve feltételezhetően a könyv ezen szakaszának olvasásakor az olvasó már olyan tudás birtokában van, amivel megbirkózhat egy MSDN szintű dokumentáció olvasásával. Az MSDN reflection dokumentációja a https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection címen érhető el.