fel
le

Átalakítás - parse

A konzolról beolvasás eredménye minden esetben egy string. De más esetek is előfordulhatnak, amikor rendelkezünk valamilyen adattal, de a tároló típusunk a string. A string formában tárolt (esetleg más típusú) adatainkat az adott típusra hozni szükséges lépés lehet, hiszen string alakjukban nem lehet velük műveleteket végezni:

Console.Write("Kérem a számot:");
string x = Console.ReadLine();
Console.Write("A szám kétszerese = {0}", x*2); // hibás
 

A fenti példában a x*2 nem értelmezhető kifejezés, így szintaktikai hibás. Nem lehet ugyanis egy string típusú adatot megszorozni (sem osztani) egész számokkal.

A stringekből egyéb típusokba átalakítás nem triviális lépés. A szokásos explicit típuskényszerítés ez esetben nem működik, hiszen az adatok tárolása a cél típustól (pl. int) nagyon távoli. Ráadásul nem is feltétlenül kivitelezhető az átalakítás másik típusba, csak ha a string alakban tárolt karaktersorozat értelmezhető az adott típusra. Egész számmá átalakítás esetén a string minden egyes karaktere csakis számjegy lehet!

Az átalakítást ez esetben függvények végzik. A függvényeknek oda kell adni a string alakú értéket, azok megpróbálják ellenőrízni, hogy helyes formátumú-e az adat, és eredményül már kíván típusú átalakított értéket adják vissza.

Az átalakító függvényeket - melyek a string típusból a kívánt típusba alakítanak át - közös néven parse függvényeknek nevezzük.

A parzolás a string átalakítása adott típusra. Az átalakító függvényeket C#-ban összegyűjtötték a Convert osztályba, így léteznek az alábbi függvények:

  • Convert.ToChar(...) // char
  • Convert.ToBoolean(...) // bool
  • Convert.ToSingle(...) // float
  • Convert.ToDouble(...) // double
  • Convert.ToSByte(...) // sbyte
  • Convert.ToInt16(...) // short
  • Convert.ToInt32(...) // int
  • Convert.ToInt64(...) // long
  • Convert.ToByte(...) // byte
  • Convert.ToUInt16(...) // ushort
  • Convert.ToUInt32(...) // uint
  • Convert.ToUInt64(...) // ulong

Ugyanakkor nincs Convert.ToString(...) melynek egyszerűen nincs értelme (string-ből string-et kellene előállítani)!

Másrészt lényegében ugyanezek a függvények megtalálhatóak az egyes típusok alapján is:

  • char.Parse(...)
  • bool.Parse(...)
  • float.Parse(...)
  • double.Parse(...)
  • int.Parse(...)

stb.

Valójában mindegy, melyik függvényt használjuk a feladat végrehajtására. Akár a Convert.ToDouble(...) akár a double.Parse(...) függvényt használjuk - mindkettő pontosan ugyanazt fogja csinálni. Ha úgy kényelmesebb, gondoljuk azt nyugodtan, hogy a két függvény ugyanaz, csak két különböző néven is elérhetőek.

A két néven létezés oka, hogy van olyan programozó, aki konvertálni akar, és ebből az irányból gondolkodva már írja is, hogy Convert., és nézi mivé lehetne konvertálni az adatot. Másik típusú programozó double-t akar kapni eredményül, már írja is hogy double. és ebből az irányból szereti megközelíteni a konvertálást.

Egész szám bekérése

Kérjünk be egy egész számot, és írassuk ki a szám kétszeresét:

Console.Write("Kérek egy egész számot:");
string s = Console.ReadLine();
int x = int.Parse( s );
Console.Write("A szám kétszerese = {0}", x*2); // ez így jó
 
Amennyiben a felhasználó figyelmetlenségből, vagy szándékosan nem egész számot ír be, úgy a program leáll futási hibával (run-time error). Az OOP világában a futási hibát Exception-nak (kivétel) nevezzük, okát a következő félévben magyarázzuk meg. Amennyiben valamely parse függvény nem képes az string formájú adat átalakítására, úgy FormatException (alaki hiba) keletkezik.

E miatt a futási hiba nem a Console.ReadLine() sorában keletkezik. A ReadLine tökéletesen ellátja egyetlen feladatát - beolvassa amit a felhasználó begépel, és eredményét ez esetben az s változóba helyezi el. A hiba a következő sorban, az int.Parse(...) végrehajtása közben jelentkezik. Az int-re alakító parse függvény észleli, hogy az s változóban lévő érték nem alakítható át egész számmá, így futási hibát jelez, és megállítja a programot ezen a ponton.

1. ábra: Futási hiba parse közben

Ugyanakkor, ha a parse kivitelezhető, az átalakítás végbemegy, a beírt számérték az s változóból átkerül az x változóba, és mivel annak típusa márt int, így az érték megszorozható 2-vel (és egyéb numerikus művelet is végezhető vele).

2. ábra: Sikeres INT parse

Tört szám bekérése

A tört érték bekérése ezen ugyanmódszer alapján működhet - csak konvertáláskor a double.Parse(...) vagy a Convert.ToDouble(...) függvényeket kell használni. A parse eredményét így értelemszerűen egy double típusú változóban kell fogadni.

Console.Write("Kérek egy tört számot:");
string s = Console.ReadLine();
double x = double.Parse( s );
Console.Write("A szám kétszerese = {0}", x*2);
 
Jegyezzük meg, hogy a double.Parse(...) függvény nagyon intelligens. A parse során figyelembe veszi, hogy a felhasználó milyen nyelvterületen él, és milyen tizedestört jelölési módszert ismer. Magyar felhasználók esetén a tizedespont helyett a tizedesvesszőt ismeri fel.
3. ábra: A tizedespont nem helyes!

Ez sajnos azt okozza, hogy a programozók tesztelés közben folyton elrontják az adatbevitelt - hiszen ők hozzá vannak szokva a tizedespont használatához. A double parse egyébként nem onnan ismeri fel a felhasználók nyelvét, hogy egy kis manó formájában kikukucskál a képernyőről, vagy belehallgatózik az msn telefonálásunkba. Maga a Windows operációs rendszerünk területi beállításai segítik a döntést.

4. ábra: Vezérlőpult / Terület és nyelvi beállítások

Ez azt is jelenti, hogy a kész programot (.exe) egy olyan számítógépre másolva, amelyen angol területi beállítású Windows fut - a double.Parse(...) függvény automatikusan a tizedespontot keresi és ismeri fel adatbevitel közben.

A C# nyelvi szintaktika előírja, hogy a forráskódban megadott tört szám literálokban tizedespontot használjuk! Akkor is, ha a Windows-unk magyar nyelvű, magyar területi beállításokkal van telepítve! Ez a program forráskódjának hordozhatósága miatt szükséges - így a forráskód bármely Windows-on hibátlanul és ugyanúgy fordítható a Visual Studió által. Az adatbevitel az más kérdés. A tört szám beírása során használt formátum már függ az adott Windows nyelvi beállításaitól.

Amennyiben a területi beállításoknak megfelelően a beíráskor tizedesvesszőt használunk - úgy a beolvasás és átalakítás már problémamentes:

5. ábra: Sikeres double parse

Amennyiben szeretnénk magunkat a Windows területi beállításoktól függetleníteni magunkat - arra is van megoldás. Helyezzük el az alábbi sort a programunk elején. Ezzel rávesszük, hogy a Windows aktuális területi beállításai helyett az USA angol területi beállításait használja a .NET Framework, így a parse függvények is.

System.Threading.Thread.CurrentThread.CurrentCulture =
  new System.Globalization.CultureInfo("en-US");
 

Másik lehetséges megoldás, hogy amennyiben a beíráskor tizedespontot használnánk - úgy azt lecseréljük vesszőre. Erre a string-ek replace függvénye alkalmas - csak meg kell adni hogy az s stringben szereplő (pont) karaktereket cserélje ki a (vessző) karakterekre. Így a beíráskori tévesztést a program korrigálja.

string s = Console.ReadLine();
s = s.Replace( '.' , ',' );
double x = double.Parse(s);
 

Persze nem kell ezt ennyi sorba írni. Kihasználhatjuk, hogy a Console.ReadLine() függvény eredménye már maga is string típusú, tehát eleve alkalmazzuk rá a replace-t:

string s = Console.ReadLine().Replace('.',',');
double x = double.Parse(s);
 

Illetve alkalmazhatjuk azt is, hogy a beolvasott értéket nem helyezzük el egy változóba csak azért, hogy később odaadhassuk a parse-nak, hanem a readline függvény értékét direkt módon adjuk oda a parse-nak (ügyeljünk a zárójelezésre)!

double x = double.Parse( Console.ReadLine().Replace('.',',') );
 

Keveredés - double / int

Érdekes kérdés lehet, mi történik, ha a double szám beolvasása során mégsem az double.Parse(...) -t, hanem az int.Parse(...) -t használjuk átalakításhoz. Logikusan gondolkodva az int.Parse(...) mit sem tud arról, hogy eredményét egy double változóba kívánjuk rakni, az ő dolga a string ellenőrzése hogy megfelel-e az int alaki szabályainak, és ha nem, akkor futási hibát okozni. Ha az egyébként egész szám alakú, akkor elvégzi a konverziót, és eredményül adja a kapott egész számot. Ezt az értékadás utasítása fogadja, majd implicit típuskonverziót végez, és a kapott double-t helyezi el az x változóba.

string s = Console.ReadLine();
double x = int.Parse( s ); // ez elvileg jó!
 

A fenti működés lényegében megegyezik az alábbi lépéssorozatnak:

string s = Console.ReadLine();
int t = int.Parse( s );
double x = t;
 

Vagyis elvileg szabad keverni a double és int működést ebben a formában, de az int.Parse sosem fog tört számokat felismerni.

Fordítva szintaktikai hibás lenne, hiszen a double.Parse eredménye egy double típusú érték, melyet az értékadó utasítás nem fogad el - típushibás (int = double alakú értékadások ilyenek)!

string s = Console.ReadLine();
int x = double.Parse( s ); // hiba!
 

Logikai adat bekérése

A bool.Parse(...) függvény képes logikai adatok bekérésére. Sajnos nincs ilyen Windows területi beállítás, amely megadná, hogy mondják magyarul az igaz/hamis szavakt, így beíráskor szintén a true és false szavakat kell használni - a bool.Parse csak ezt érti. Annyi előnyünk van csak, hogy nem kell a kis/nagybetűkre ügyelni - a false/False/FALSE esetek mindegyike megfelel (hasonlóan a true-ra is).

Console.Write("Kérek egy logikai értéket:");
string s = Console.ReadLine();
bool x = bool.Parse(s);
Console.Write("Az értéke negáltja = {0}", !x);
 
6. ábra: Sikeres BOOL parse

Karakter adat bekérése

A karakterek bizonyos szempontból egy hosszú stringekre hasonlítanak, de a memóriában másképpen vannak tárolva. A char parse működhetne úgy is, hogy a beírt karaktersorozat első betűjét leválasztja, majd az első karaktert adja eredményül - de nem így működik (string must be exactly one character long - a stringnek pontosan egy betű hosszúnak kell lennie)!

7. ábra: Hibás CHAR parse - két betű az input

Ugyanígy hibás ha egyetlen betűt sem írunk be! Ha jó bevitelt szeretnénk elérni, akkor pontosan egy karakternyit kell beírnunk az enter leütése előtt:

8. ábra: Sikeres CHAR parse

Persze karakter beírására van más mód is. Ez a Console.ReadLine nem igazán alkalmas erre, hiszen az egy betű beírása után még enter-t is kell ütni. Sokkal jobb módszer a Console.ReadKey() függvény alkalmazása, amely nem csak megvárja egy billentyű leütését - de meg is adja eredményül a leütött billentyűvel kapcsolatos információkat. Ezt egy ConsoleKeyInfo típusú változóban lehet fogadni, melynek van egy KeyChar része - ez tartalmazza magát a leütött karaktert. Ekkor nincs szükség a parse függvényre, hiszen nem kaptunk sehol string-t.

Console.Write("Kérek egy betűt értéket:");
ConsoleKeyInfo s = Console.ReadKey();
char x = s.KeyChar;
Console.WriteLine();
Console.Write("Amit beírtál, az a [{0}] betű", x);
Console.ReadKey();
 

Hibás adatbevitel elkerülése

Állandó problémánk, hogy ha a felhasználó nem megfelelő formátumú adatot gépel be (pl. egész számot kérünk be, de nem azt ír be), úgy a program leáll futás közbeni hibával. A problémát jellemzően a következő félévben ismertetett try - catch utasításokkal, kivételkezeléssel lehet kezelni. Ugyanakkor van egy köztes megoldás is, melynek segítségével e félévben is képesek lehetünk a hibát kezelni. Ehhez nem a közönséges Parse(...) függvényt, hanem a fejlettebb TryParse(...) függvényt kell használnunk.

A TryParse a neve szerint is először próbál parse-olni. Amennyiben sikerül, úgy megkapjuk a parse eredményét egy változóban, és true értékkel jelzi a sikert. Ha sikertelen volt a tryparse (hibás formátumú adatbevitel), úgy a program futása nem áll le, a változóban ekkor értelemszerűen nem kapunk adatot meg, viszont a false érték fogja jelezni a sikertelenséget.

Mivel a TryParse ennek megfelelően két visszatérési értékkel is rendelkezik (a parse eredménye, illetve a true/false sikeresség-jelző), a hívása szokatlan:

int x;
if (int.TryParse( Console.ReadLine(), out x)==true)
{
   // sikeres parse, az x-ben van a beírt számérték
}
else
{
   // sikertelen parse, hibás adatbevitel
}
 

Ennek segítségével lehet olyan ciklust készíteni, melynek segítségével garantáltan lehet számot bekérni:

int x;
bool siker=false;
while (siker==false)
{
   if (int.TryParse( Console.ReadLine(), out x))
     siker=true;
}
 

Vagy a break segítségével:

int x;
while (true)
{
   if (int.TryParse( Console.ReadLine(), out x))
     break;
}
 

Illetve a legravaszabb (figyeljük a pontosvesszőt a while ciklus törzsének helyén, csak profik számára ajánljuk):

int x;
while (int.TryParse( Console.ReadLine(), out x)) ;
 
Hernyák Zoltán
2013-01-24 10:33:26