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:
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...
a robotika mindhárom törvényét megsértve :)
Megjegyzés küldése