MySQL Java JavaScript PHP Python HTML-CSS C-sharp

DynamicObject și ExpandoObject

C# și .NET oferă interesante posibilități de dezvoltare folosind DLR (Dynamic Language Runtime), printre care se numără și utilizarea spațiului de nume System.Dynamic, în special clasa ExpandoObject. Aceasta permite crearea de obiecte dinamice, similare celor utilizate în JavaScript:

// definim un obiect care va stoca diverse valori
dynamic person = new System.Dynamic.ExpandoObject();
person.Name = "Tom";
person.Age = 46;
person.Languages = new List<string> { "english", "german", "french" };

Console.WriteLine($"{person.Name} - {person.Age}");
foreach (var lang in person.Languages)
   Console.WriteLine(lang);

// declarăm o metodă
person.IncrementAge = (Action<int>)(x => person.Age += x);
person.IncrementAge(6); // creștem vârsta cu 6 ani
Console.WriteLine($"{person.Name} - {person.Age}");

Ieșirea în consolă:

Tom - 46
english
german
french
Tom - 52

Cu ExpandoObject, putem declara orice proprietăți, cum ar fi Name, Age, Languages, care pot reprezenta diverse tipuri de obiecte. În plus, putem defini metode folosind delegați. Aceste metode pot fi invocate la fel de simplu cum sunt invocate metodele definite într-o clasă tradițională.

DynamicObject

Pe lângă ExpandoObject, o altă clasă importantă este DynamicObject, care permite crearea de obiecte dinamice, dar oferă mai mult control și flexibilitate decât ExpandoObject, fiind potrivit pentru situații mai complexe.

Pentru a folosi DynamicObject, trebuie să creăm o clasă care să moștenească DynamicObject și să suprascrie metodele sale, cum ar fi:

  • TryBinaryOperation(): efectuează o operație binară între două obiecte
  • TryConvert(): efectuează o conversie la un anumit tip
  • TryCreateInstance(): creează o instanță a obiectului
  • TryDeleteIndex(): șterge un indexator
  • TryDeleteMember(): șterge o proprietate sau metodă
  • TryGetIndex(): obține un element prin indexator
  • TryGetMember(): obține valoarea unei proprietăți
  • TryInvoke(): invocă un obiect ca delegat
  • TryInvokeMember(): invocă o metodă
  • TrySetIndex(): setează un element prin indexator
  • TrySetMember(): setează o proprietate
  • TryUnaryOperation(): efectuează o operație unară

Fiecare dintre aceste metode are același model de definire: toate returnează o valoare logică, indicând dacă operația a reușit. Ca prim parametru, toate acceptă un obiect binder. Dacă metoda reprezintă un apel al unui indexator sau al unei metode a unui obiect care poate primi parametri, ca al doilea parametru se folosește un array object[] – acesta stochează argumentele transmise metodei sau indexatorului.

Aproape toate operațiile, cu excepția setării și eliminării proprietăților și indexatorilor, returnează o valoare specifică (de exemplu, dacă obținem valoarea unei proprietăți). În acest caz, se folosește al treilea parametru out object value, care este destinat stocării obiectului returnat.

De exemplu, definiția metodei TryInvokeMember():

public virtual bool TryInvokeMember (InvokeMemberBinder binder, object?[]? args, out object? result)

Parametrul InvokeMemberBinder binder este un binder – obține proprietățile și metodele obiectului, object?[]? args stochează argumentele transmise, out object? result este destinat stocării rezultatului final.

Să analizăm un exemplu. Să creăm o clasă pentru un obiect dinamic:

using System.Dynamic;

class PersonObject : DynamicObject
{
   // dicționar pentru stocarea tuturor proprietăților
   Dictionary<string, object> members = new Dictionary<string, object>();

   // setarea unei proprietăți
   public override bool TrySetMember(SetMemberBinder binder, object? value)
   {
       if(value is not null)
       {
           members[binder.Name] = value;
           return true;
       }
       return false;
   }

   // obținerea unei proprietăți
   public override bool TryGetMember(GetMemberBinder binder, out object? result)
   {
       result = null;
       if (members.ContainsKey(binder.Name))
       {
           result = members[binder.Name];
           return true;
       }
       return false;
   }

   // apelarea unei metode
   public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
   {
       result = null;
       if(args?[0] is int number)
       {
           // obținem metoda după nume
           dynamic method = members[binder.Name];
           // apelăm metoda, transmițându-i valoarea args?[0]
           result = method(number);
       }
       // dacă result nu este null, apelul metodei a fost reușit
       return result != null;
   }
}

Clasa moștenește din DynamicObject, deoarece nu putem crea direct obiecte de tip DynamicObject. De asemenea, aici sunt suprascrise trei metode moștenite.

Pentru stocarea tuturor membrilor clasei, atât proprietăți, cât și metode, este definit un dicționar Dictionary<string, object> members. Cheile sunt numele proprietăților și metodelor, iar valorile – valorile acestor proprietăți.

În metoda TrySetMember() are loc setarea unei proprietăți:

bool TrySetMember(SetMemberBinder binder, object? value)

Parametrul binder stochează numele proprietății care se setează (binder.Name), iar value – valoarea care trebuie setată.

Pentru obținerea valorii unei proprietăți, este suprascrisă metoda TryGetMember:

bool TryGetMember(GetMemberBinder binder, out object? result)

Din nou, binder conține numele proprietății, iar parametrul result va conține valoarea proprietății obținute.

Pentru apelul metodelor, este definită metoda TryInvokeMember:

public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
{
   result = null;
   if(args?[0] is int number)
   {
       // obținem metoda după nume
       dynamic method = members[binder.Name];
       // apelăm metoda, transmițându-i valoarea args?[0]
       result = method(number);
   }
   // dacă result nu este null, apelul metodei a fost reușit
   return result != null;
}

Mai întâi, obținem metoda folosind binderul, apoi îi transmitem argumentul args[0], convertindu-l anterior la tipul int, și setăm rezultatul metodei în parametrul result. Așadar, în acest caz, se presupune că metoda va primi un parametru de tip int și va returna un rezultat. Dacă metoda returnează true, considerăm că apelul metodei a fost reușit.

Acum să folosim clasa în program:

using System.Dynamic;

// creăm obiectul
dynamic person = new PersonObject();
// setăm o serie de proprietăți
person.Name = "Tom";
person.Age = 23;
// definim o metodă pentru a modifica proprietatea Age
Func<int, int> increment = (int n) => { person.Age += n; return person.Age; };
person.IncrementAge = increment;

Console.WriteLine($"{person.Name} - {person.Age}"); // Tom - 23
person.IncrementAge(4); // aplicăm metoda
Console.WriteLine($"{person.Name} - {person.Age}"); // Tom - 27

Expresia person.Name = "Tom" va apela metoda TrySetMember, în care al doilea parametru va fi șirul de caractere "Tom".

Expresia return person.Age; va apela metoda TryGetMember.

De asemenea, obiectul person are definită metoda IncrementAge, care reprezintă acțiunile expresiei lambda (int n) => { person.Age += n; return person.Age; };. Această expresie primește un număr n, mărește cu acest număr proprietatea Age și returnează noua valoare a lui person.Age. Și, la apelul acestei metode, se va apela metoda TryInvokeMember. Astfel, va avea loc incrementarea valorii proprietății person.Age.

← Lecția anterioară Lecția următoare →