Mostenire vs. clase partiale vs. metode de extensie
Posibilitati de extindere a codului
Exista mai multe metode de a extinde cod, nu doar cod scris de voi dar si cod existent sub forma de biblioteci. Mostenirea este un mijloc prin care se pot extinde functionalitatiile unor clase, insa exista cazuri unde mostenirea este complet depasita de situatie. Vom prezenta doua alternative la extinderea functionalitatii codului pe langa mostenire, anume clase partiale si metode de extensie
Clase partiale
Principiul claselor partiale (partial classes) este simplu, putem sparge o clasa din acelasi namespace in mai multe fisiere sursa aplicand cuvantul cheie partial pe clasa, practic putem avea declarat intr-un fisier clasa cu o parte din logica iar in altul restul metodelor/proprietatilor/campurilor.
// Putem sa declaram aceasta clasa partiala in fisierul SplitClass.cs
public partial class SplitClass
{
public SplitClass(int field)
{
Field = field
}
}
// Restul clasei o putem declara in fisierul SplitClass.Properties.cs
public partial class SplitClass
{
public int Field { get; }
}
Vine intrebarea de ce ne-ar trebui clase partiale declarate in fisiere separate daca putem sa le declaram intr-unul singur pentru ca nu reprezinta un dejavantaj ci complica urmarirea codului. Motivul este ca pot exista unelte de generare a codului care sa creeze dandu-se o parte din clasa restul de proprietati si metode necesare in mod automat. Daca clasa este partiala restul implemenetarii generate poate sta in alta parte fara sa ne lovim de acesta care poate fi foarte complicata fiind optimizata si peste care nu ar trebui sa intervenim. De exemplu, in .NET MAUI pentru a implementa diverse componente de interfata o parte este generata automat pe baza unor fisiere XML iar restul implementarii ramane sa fie completata de catre dezvoltator.
Pe langa asta, daca avem metode si proprietati private le avem accesibile pentru a implementa logica noi sau pe baza carora generatoarele de cod pot crea logica sau generatoarele le genereaza si noi le putem folosi. Daca s-ar folosi mostenire, modificatorul de acces privat sau protected ar functiona doar intr-o singura directe, nu in ambele cand noi totusi nu vrem sa expunem acele campuri catre alte clase.
Metode de extensie
Mai utilizate decat clase partiale sunt metodele de extensie (extension methods). O metoda de extensie este o metoda statica dintr-o clasa statica care poate fi apelata ca o metoda a altei clase. Prin aceasta abordare putem sa extindem clase deja existende cu noi metode fara mostenire dar avandu-le la indemana ca orice alte metode ai clase pentru a fi mai elegant codul si usor de urmarit.
// Cream o clasa statica unde sa tinem grupate metodele de extensie
public static class IEnumerableExtensions
{
// Cream metoda statica si ii punem primul parametru cu cuvantul cheie "this" in fata pe langa ceilalti parametri
public static IEnumerable<TOut> Map<TIn, TOut>(this IEnumerable<TIn> enumerable, Func<TIn, TOut> selector)
{
foreach (var item : enumerable)
{
yield return selector(item);
}
}
public static IEnumerable<T> Filter<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
{
foreach (var item : enumerable)
{
if (predicate(item)) {
yield return item;
}
}
}
}
IEnumerable<int> items = new int[] { 0, 1, 2, 3, 4 };
Console.WriteLine("The projected items are: ");
foreach (var item : items.Filter(e => e % 2 = 1).Map(e => (3 * e).ToString()))
{
Console.Write("{0}, ", item);
}
Dupa cum se vede, am declarat o metoda de extensie si am folosit-o ca orice alta metoda a interfetei IEnumerable<T>. Lucrul acesta este posibil pentru ca nu exista distinctie intre metode statice si non-statice din punct de vedere al compilatorului, metodele non-statice difera doar prin acel parametru this ascuns. Aici se face acest artificiu punand in aceasta metoda statica un prim parametru cu cuvantul cheie this in fata ca sa simuleze o metoda non-statica a unei clase.
Metodele de extensie au limitarea ca nu au acces la campuri non-publice dar sunt extrem de utile si des folosite in C# pentru ca extind cod deja existent si face nu doar ca cod nou sa fie compatibil cu cel vechi dar sa si ofere un artificiu prin care sa para ca au fost adaugate functionalitati noi intr-un mod transparent si sigur.
Un exemplu este LINQ, IEnumerable<T> ca interfata a existat cu mult timp inainte de metode de extensie, la fel si multe colectii. Daca s-ar fi adaugat alte metode care sa fie mostenite ar fi dus la incompatibilitatea codului intermediar citit de runtime din diverse motive (este legat de modul de implementare a metodelor mostenite prin virtual functuion tables) si ar fi facut multe biblioteci incompatibile cu aplicatii care le folosesc. Astfel, metodele de estensie rezolva intr-un mod foarte simplu aceasta problema fara sa genereze incompatibilitati si fara a rescrie cu clase si interfete noi bibliotecile existente.
Metodele de extensie sunt bune pentru atat a pastra compatibilitate inversa cat si pentru a imbunatatii calitatea si intelegearea codului. Un pattern care se poate folosi prin metode de extensie este inlantuirea de metode (method chaining) ca in metodele de LINQ sau folosind acest pattern sa conduca la builder pattern, adica pe baza unui obiect complex sa inlantuim metode pentru a-l configura.