Skip to content

Reflection și obiecte dinamice

31 May 2008

În cele ce urmează vreau să prezint un exemplu de lucru cu obiecte dinamice folosind Reflection. Nu ține atât de mult de compilatoare cât de interpretoare, unde, pe baza unor șiruri de caractere, instanțiem obiecte și facem apeluri de metode.

Avem clasa System.Type care încapsulează informații despre tipuri, precum și clasele ConstructorInfo și MethodInfo derivate din MethodBase care încapsulează informații despre constructori, respectiv metode, clase pe care le găsim în namespace-ul System.Reflection.

Pentru a crea de exemplu o instanță a clasei StringBuilder și a concatena două șiruri de caractere, avem următoarele linii de cod:

ConstructorInfo ci = Type.GetType("System.Text.StringBuilder").GetConstructor (Type.EmptyTypes);
object test = ci.Invoke(null);

MethodInfo mi = test.GetType().GetMethod("Append", new Type[] { typeof(string) });
mi.Invoke(test, new object[] { "Hello " });
mi.Invoke(test, new object[] { "World!" });

Console.WriteLine(test);

Se observă că, din punct de vedere al compilatorului de C#, StringBuilder nu apare decât într-un șir de caractere iar obiectul utilizat este de tipul de bază object. Prima instrucțiune întoarce o instanță ConstructorInfo ce corespunde constructorului fără parametri a clasei StringBuilder. Metoda statică Type.GetType returnează instanța System.Type corespunzătoare tipului identificat printr-un șir de caractere. GetConstructor primește ca parametrii un vector de tipuri care reprezintă tipurile argumentelor – în cazul nostru nu avem argumente așa că folosim Type.EmptyTypes care este, de fapt, echivalent cu new Type[0] dar ne scutește de instanțiere.

Invoke apelează metoda cu o listă de argumente, null în cazul nostru și întoarce un obiect (System.Object). Am creat astfel dinamic o instanță StringBuilder.

Similar, luăm instanța System.Type corespunzătoare obiectului – test.GetType() – și apelăm GetMethod care primește un nume de metodă și o listă de argumente. Căutăm metoda Append cu un singur argument de tip string.

Aici, la apelul Invoke trebuie să precizăm și instanța cu care apelăm metoda (obiectul test) și argumentele.

Ultima linie va afișa pe ecran “Hello World!”.

Dacă snippet-ul de mai sus pare încă destul de hard-coded, putem, în locul lui, introduce următoarele:
un dicționar care să rețină instanțele pe care le-am creat, o metodă pentru a construi obiecte și o metodă pentru apeluri astfel:

private static Dictionary objects = new Dictionary();

public static void Create(string name, string type)
{
    ConstructorInfo ci = Type.GetType(type).GetConstructor(Type.EmptyTypes);
    object obj = ci.Invoke(null);

    objects.Add(name, obj);
}

public static void Call(string name, string method, params string[] arguments)
{
    // Aici construim tipurile pe baza argumentelor
    Type[] args = new Type[arguments.Length];

    for (int i = 0; i < args.Length; i++)
    {
        // Doar tipul string
        args[i] = Type.GetType("System.String");
    }

    MethodInfo mi = objects[name].GetType().GetMethod(method, args);
    mi.Invoke(objects[name], arguments);
}

Metoda Create primește ca argumente un nume de variabilă și un tip și apelează constructorul fără parametri al tipului, metoda Call primește numele variabilei, numele metodei și o listă de argumente de tip string. M-am limitat în exemplu la constructori fără parametri și metode ce primesc argumente string nu pentru că Reflection nu mi-ar permite mai mult, doar că, pentru a determina alte tipuri doar pe baza unor string-uri am avea nevoie de un parser (care să determine, de exemplu, că “10.5” e de tip float).

Acum putem scrie “Hello World!” astfel:
 
static void Main(string[] args)
{
    Create("test", "System.Text.StringBuilder");
    Call("test", "Append", "Hello ");
    Call("test", "Append", "World!");

    Console.WriteLine(objects["test"]);
}

Și nu numai:

static void Main(string[] args)
{
    Create("test", "System.Text.StringBuilder");
    Call("test", "AppendFormat", "{0} {1}!", "Hello", "World");

    Console.WriteLine(objects["test"]);
}

Astfel se crează obiecte și se apelează metode dinamic utilizând Reflection. Desigur, se pot face multe altele – de exemplu putem seta valori la diferite atribute ale obiectelor instanțiate. Un dezavantaj ar fi timpul de execuție – codul compilat (MSIL) rulează mult mai repede decât apelurile Invoke dar pentru un interpretor aceasta ar fi una din posibilitățile de implementare.

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: