Kezdőlap > Programming > .NET opcionális metódus paraméter trükk

.NET opcionális metódus paraméter trükk

2010. április 5. hétfő
A .NET/C# 4.0-tól több örvendetes lehetőséggel is bővül a metódusok paraméter megadási lehetősége. Egyik az opcionális, a másik az úgynevezett nevesített paraméterek vagy argumentumok. Azonban egyiket se lehet esztelenül, a háttérben lezajló folyamatok ismerete nélkül alkalmazni. Vegyük például az opcionális paraméter kérdéskört.
 
Tételezzük fel, hogy van egy cikket (terméket) tartalmazó osztályunk, abban egy mennyiséget meghatározó metódus (tekintsünk el attól, hogy ez most valós, vagy jó, vagy épp rossz példa, csak egy példa).
public class Cikk
{
  public void CikkMennyiseg(string cikkszam, int minimum = 0)
  {
    // A részeleteket elhagytuk…
  }
}
Ezt a metódust kétféleképpen is meghívhatjuk:
CikkMennyiseg("Kalapács");     // Az opcionális paraméter szerinti minimum mennyiség 0 lesz
CikkMennyiseg("Kalapács", 15); // A paraméter szerinti minimum mennyiség 15 lesz
Ha nem adtuk meg a minimum mennyiség értéket (első sor), akkor az az opcionális paraméternek köszönhetően 0 lesz, ha meg odaírtuk (második sor), akkor annyi, amennyi (jelen esetben 15). Na, de ismét meg kell jegyeznem, hogy csak emiatt nem töltöttem volna el 90 percet ezen bejegyzés megírásával. Akkor meg miért? Azért, mert arra gondoltam érdemes lenne megnézni mit csinál a C# fordító az első utasítással. Íme:
CikkMennyiseg("Kalapács", 0);
Hát igen, semmi meglepő sincs benne. A metódus hívásakor ugyan nem írtuk oda a minimum értéket, de hála az opcionális paraméternek a fordító megtette ezt helyettünk. Itt jegyezném meg, hogy fordítás után a Cikk osztályt tartalmazó szerelvény metaadatai közé (az IL – Internediate Language kódba) a CikKMennyiseg metódus a string és int paraméterekkel együtt tárolódik le, vagyis futtatáskor a CLR (Common Language Runtime) semmit se tud arról, hogy az int minimum lánykorában még egy opcionális paraméter volt (a számára már két, kötelezően megadandó paraméter létezik).
 
Ok. Ez még mindig nem olyan nagy szám. Nézzük tovább a  lehetséges eseteket.
 
Tételezzük fel, hogy van két szerelvényünk, egy "A" (A.DLL) és egy "B" (B.DLL).  Az a Cikk osztályunk, amiben a CikkMennyiseg metódus szerepel benne van az "A" szerelvényben, a "B" szerelvény pedig hivatkozik rá és használja. 
using A;                             // Innen érhető el a Cikk osztály
 
namespace B
{
  public class CikkMenedzser
  {
    public void Eladas()
    {
      // Létrehozunk egy cikk objektumot
      Cikk cikk = new Cikk();
      // Meghívjuk a cikk objektum CikkMennyiseg metódusát
      cikk.CikkMennyiseg("Kalapács");  // Ebből cikk.CikKMennyiseg("Kalapács, 0) lesz
      // A többi lényegtelen…
    }
  }
}
Ok. Eddig megvolnánk, de most jön a lényeg. A Cikk osztály fejlesztője úgy dönt, hogy kibővíti a CikkMennyiseg metódus paraméterlistáját még egy opcionális paraméterrel, melynek neve legyen int maximum. Íme:
public void CikkMennyiseg(string cikkszam, int minimum = 0, int maximum = 100)
{
 
// A részeleteket megint elhagytuk…
}
Frankó. Ez fordításkor úgy, ahogy van bekerül az "A" szerelvény metaadatai közé, és mint tudjuk kissé átalakítva, immár három string, int, int típusú kötelező paraméterként (ne felejtsük el, hogy az opcionális lehetőség csak egy C# fordítói trükk vagy segítség, azaz nem része a .NET CLR-nek). Vajon mi történik akkor, amikor a "B" szerelvényből (aki természetesen máris használja az új "A" szerelvényt) ismét meghívjuk a CikkMennyiseg metódust valahogy így:
cikk.CikkMennyiseg("Kalapács");  
Józan ész szerint azt gondolnánk, hogy semmi különös. A hiányzó opcionális mimimum és maximum paraméterek behelyettesítődnek 0-val és 100-al, aztán uccu neki. Íme:
cikk.CikkMennyiseg("Kalapács", 0, 100);
De jó is lenne! Sajnos azonban a hívó "B" szerelvény ekkor még semmit se tud arról, hogy az "A" szerelvényen belüli Cikk osztály CikkMennyiseg metódusának lett egy új, opcionális maximum paramétere, vagyis a C# fordító továbbra is az általa ismert módon állítja össze az utasítást. Íme: 
cikk.CikkMennyiseg("Kalapács", 0);  // A "B" szerelvény által kezdeményezett hívás
Erre viszont az "A" szerelvény (ahol valójában a Cikk osztály és annak metódusai vannak) elég morcosan fog reagálni, hiszen neki ilyen paraméterekkel rendelkező metódusa már égen-földön nincs, bármennyire is igyekszik találni egyet. Na, de akkor milyenje van? Ilyenje:
CikkMennyiseg(string, int, int)     // Az "A" szerelvény Cikk osztályában lévő való világ
Ez meg ugye barátok közt se egyezik a "B" szerelvény által kezdeményezett változattal, hiszen ott a fordító csak két paramétert tett bele a kódba string és int típussal, vagyis az "A" szerelvény által elvárt harmadik int tipusú maximum paraméter hiányzik. A CLR ezt a hanyagságot csúnya kivétellel hálálja meg. Na, de akkor mi a megoldás?
 
A megoldás sajnos az, hogy a CikkMennyiseg metódus kibővítése után a "B" szerelvényt is újra kell fordítani (sőt, az összes CikkMennyiseg-re  hivatkozó szerelvényt is) ahhoz, hogy a  hivatkozók metaadatai közé is bekerüljön az új int maximum paraméterrel rendelkező metódus, azaz fordításkor a 0 mellé legenerálódjon a másik 100 is abban az esetben, ha a programozó ezt nem adta meg (amihez egyébiránt minden joga megvan).
 
Így a végére jutva kissé elbizonytalanodtam, hogy vajon érthető volt-e amit írtam? Nem tudom, pedig én igyekeztem. A lényeg, hogy tökjó dolgok ezek az újdonságok, melyek láttán úgy érzem magam, mint a kisgyerek, amikor a kertben boldogan szedegeti a nyuszi által otthagyott ajándékokat egészen addig, amíg bele nem lép valamibe, aminek nem kellene ott lennie.
 
Ja, és olvasni, olvasni, olvasni, "mert az olvasás nem gyíkság…"
Kategóriák:Programming