2009. március 11., szerda

C# 3.0 parciális metódusok

A C# 3.0 egyik kevésbé sztárolt új nyelvi eleme a partial method, ami a C# 2.0-ban megismert partial class képességeit bővíti ki.
A parciális osztályok arra szolgáltak, hogy megkönnyítsék elviselni, hogy a fejlesztőkörnyezetünk segít nekünk: a kódgenerálás csodás dolog, hiszen attól menti meg a szoftverfejleszőt, amit az a legjobban utál (neki kell kézzel megcsinálni azt, amit a gép is meg tudna), ugyanakkor magában rejti a veszélyt, hogy esetleg megtörténik az, amit viszont meg a méglegeslegjobban utál, nevezetesen hogy a gép jobban tudja a frankót, mint ő, és (a robotika mindhárom törvényét megsértve) felülvágja kétnapi munkáját.

A parciális osztályokkal szépen szét lehetett választani a Visual Studio (vagy bármely más kódgeneráló tool) és a codeproject-ről máso izzadtságos munkával megírt saját kódot, anélkül, hogy az alternatív megoldások - pl. absztakt ősoszályok generálása, és abból leszármazás - kereszjeit a vállunkra vettük volna.
Az osztályok kódját szétszórhattuk több forrásfájlba, és ha mindkét helyen megjelöltük classunkat a partial kulcsszóval, akkor a fordító szépen összefésülte őket.

C# 3.0-tól az ily módon megjelölt osztályokban lehetőségünk van metódusokat is partial-ként megjelölni. Ezek a metódusok első megközelítésben talán az absztrakt metódusokra hasonlítanak leginkább: típusmódosító nélküli, a partial kulcsszóval megjelölt metódusdefiníciók:

// Auto-generated part:
public partial class PartialExampleType
{
    public void GeneratedMethod()
    {
        Console.WriteLine("GeneratedMethod() calls PartialMetod()");
        PartialMethod();
    }

    partial void PartialMethod();
}
A példában egy parciális osztály generált része látható (i'm a liar: valójában én írtam), ami áll egy egy implementációstul legenerált, és egy "absztrakt" parciális metódusból. Az is rögtön látható, hogy az absztraktokhoz hasonlóan a parciális metódusok is meghívhatók annak ellenére, hogy a metódus konkrét implementációja még nem ismert.

Nézzük az általunk írt részt:

// Developer-written part:
public partial class PartialExampleType
{
    partial void PartialMethod()
    {
        Console.WriteLine("PartialMethod (\"{0}\") called.");
    }
}
Itt egy azonos szignatúrájú, de konkrét megvalósítással is rendelkező metódust láthatunk - az azonos metódusszignatúra előállítását az Intellisense a partial kulcsszó begépelése után megtámogatja (hasonló módon az override-hoz).
A metódustörzset sajnos nekünk kell megírni.

A fordító, ahogy a parciális osztályokat, a parciális metódusokat és azok hívásait is szépen összefésüli.

Eddig jól elvontunk az absztrakt hasonlattal, de most lássuk a különbségeket: egy absztrakt osztályból való leszármazáskor kötelező megvalósítani az absztrakt metódusokat, különben futásidőben nem lenne mit meghívni. A párhuzam itt kezd derékszögesedni, ugyanis a parciális osztályoknál/metóduoknál egyrészt nincs öröklődés ("forráskód-megosztásról" beszélünk, azaz compile-time eldől minden), másrészt az ilyen parciális metódusokat nem kötelező "megvalósítani" - ha nincs meg a "absztrakthoz" való, szignatúrában megegyező pár, a fordító egyszerűen nem fordítja be a hívását.
Felmerülhet a kérdés, hogy oké-oké, nem fordítja be a hívást egy magában álló, PartialMethod(); jellegű hivatkozásnál, de mi van, ha a metódus visszatéréséri értékére mi logikát építünk (gondolok itt a if (PartialGetIsValid()) { ... } jellegű szerkezetekre)?
A helyzet nagyon hasonló, mint a [ConditionalAttribute]-tal megjelölt metódusoknál, amik egy-egy szimbólum definiáltságától függően fordítódnak be vagy sem - ott úgy oldották meg a dolgot, hogy a conditional metódusoknak kötelező void visszatérési értékkel rendelkezőknek lenniük, és annyit elárulhatok, hogy itt sem másképp.
Az out paraméterek is tilosak.

Ha egy kicsit más megközelítésből vizsgáljuk a parciális metódusokat - nevezetesen, hogy mégis, mire jók -, azt látjuk, hogy a kódgenerátoroknak lehetőségük van nem csak effektív kód generálására, hanem egyfajta metódus-templatek definiálására, illetve ezen template-ek hívásának beépítésére a generált kódba - mi pedig a parciális osztály ránk eső részében vagy kitöltjük ezeket az "üres templateket", vagy nem.
Ebben van egy kicsi "eseménykezelő" jelleg, egy ORM kódot generáló tool pl. valószínű fog nekünk generálni mindenféle Load, Save, és egyéb hasonló metódusokat, amiknek az elejére-végére egy-egy OnSaving(...), OnSaved(...), OnLoading(...) hívásokat rakhat. Minket, pedig ha érdekel az adott "esemény", akkor megvalósítjuk a "kezelőjét" (amit azért látni kell: ez a példa ugyanolyan sánta, mint az absztaktos, hiszen szó nincs futásidőben összehergelt eseményforrásról meg feliratkozóról, fordítási időben minden eldől).

Ha megvizsgáljuk, hogy mik voltak a C# 3.0 nyelvi újdonságait kitermelő mögöttes igények és okok, általában azt találjuk, hogy a világ jobb hellyé tétele, a fejlesztők életének megkönnyítése volt a cél, meg hogy bírjon működni a LINQ.
Ha esetleg a LINQ to SQL generált osztályaiban parciális metódusokkal találkozunk, az csak a véletlen műve lehet...

2 megjegyzés:

Miklós írta...

A Linq to Sql generált kód szépsége (?) az, ha jól láttam, hogy ha te kifejted valamelyik partial függvényét (pl. InsertMyEntity), akkor az fut le, ha meg nem, akkor a beépített Insert. Vagyis neked arra figyelni kell, hogy meghívd a belső logikát (ExecuteDynamicInsert), ha csak hozzá akarsz tenni, és nem teljesen felülírni a default működést...

Imi írta...

a robotika mindhárom törvényét megsértve :)

Megjegyzés küldése