C# İstisna ve Hata Yakalama


İstisna ve Hata Yakalama




C# ile uğraşmaya
başladığımızdan beri bir çok çalışma zamanı hataları ile
karşılaşıyoruz. Örneğin bir intereger değişkeni 0’a bölemeye
çalıştığımızda karşılaştırdığımız hata. Hatalar sınıflar
tarafından tanımlanır. Örneğin DivideByZeroException (Sıfıra
bölünme hatası) sınıfı. Temel hata sınıflarının büyük bir kısmı
System isim uzayında tanımlanır.



Eğer taşma kontrolü
açıksa taşma olduğunda OverflowException (taşma hatası) meydana
gelir. Daha öncede anlattığımız bir çok programda klavyeden
girilen değerleri bir integer değişkene atarken Parse metodunu
kullandık. Eğer klavyeden girilen değerler sayılsal olmayan
değerler içeriyorsa o zaman bir FormatException ( Biçim hatası )
meydana gelir. Bir Parse metodu ile string değişkenine bir null
değer atanırsa bir ArgumentNullException (Null argüman hatası )
gelişir. Eğer bir dizinin sınırlarının dışında bir indeksleme
yapamaya çalışırsanız veya uzunluğunun dışında bir atama
yaparsanız IndexOutOfRange hatası meydana getirirsiniz. C#
dokümantasyonu her zaman hangi hatanın hangi metot da gelişeceğini
belirler.



Biz burada şimdiye
kadar hatanın gelişiminden bahsettik. C# dilinde bu olay için aynı
zamanda C#’ta bir anahtar kelime olan throw kullanılır.



Kullanıcıya göre bir
hata meydana geldiğinde programımız çökmüştür, patlamıştır. Neyse
ki Windows’u da yanında götürmemiştir. (Biliyorsunuz 90’larda
değiliz) Kullanıcıların programımızın çöktüğünü görmesini
istemezsiniz. Aynı zamanda sizin mükemmel olmayan kodlar yazmada
kabiliyetli olduğunuzu bilmelerini de istemezsiniz. Bu durumda
programımızın en önemli avantajı program tarafından fırlatılan
hataları nazikçe geri çevirmesi olur. Bu nazik geri çevirme için
programın fırlatılan hataları yakalaması fırlatılan bu hata ile
bir şeyler yapması ve sonra programımızın mutlu yoluna devam
etmesi gerekiyor.



Hata fırlatma ve
yakalama durumları, programın çalışma zamanında meydana gelen
problemleri halletmede göz önünde bulundurulan yapısal hata
ayıklamanın tamamını oluşturur. Şimdi Decimal.Parse metodunu
düşünelim. Bu metot bir string argüman gerktirir ve bir decimal
değer döndürür. Fakat ya aldığı bu argüman harfler ve nümerik
olmayan karakterler içeriyorsa? Eski gönlerde bu metot nümerik
olmayan karakterleri göz ardı edecekti. Ya da iki değer
döndürecekti, diğer çevirme işleminin düzgün gidip gitmediğini
kontrol belirleyecek boolean olacaktı. Bazı teknikler asla sürekli
olarak tamamlanmadı. Fakat yapısal hata ayıklama ile Decimal.Parse
bize “Ben bu girdi ile işlem yapamıyorum, ve gerisi senin
problemin” diyen bir hat fırlatır.



Programın istisnanın
kullanıcıya gösterilmesini veya istisnanın kendi kendine
halledilmesini sağlayabilir. Eğer son bahsedilen seçenek yani
programın hatayı kendi halletmesi seçeneği sana daha çekici
geliyorsa try ifadesini kullan. try ifadesini kullanmanın en kolay
yolu try anahtar kelimesi ile başlayıp ardından hata
fırlatabilecek ifadeyi kapsayan bir çift süslü parantez
kullanmaktır. Bu hatayla uğraşacak diğer blok olan catch ifadesi
tarafında takip edilir.



Örneğin programımızın
kullanıcı tarafından girilen bir string’i okuduğunu ve bunu
aşağıdaki ifadeyi kullanarak double bir ifadeye çevirdiğini
düşünelim





Double dDeger = Double.Parse(Console.ReadLine()); 



Parse metodu tarafından
fırlatılan herhangi bir hatayı yakalamak için ifadeyi aşağıdaki
ifade ile değiştirebilirsiniz.





Double dDeger;

try
{
dDeger = Double.Parse(Console.ReadLine());
}

catch
{
Console.WriteLine(“Geçerli olmayan bir numara girdiniz”);
dDeger = Double.NaN;
}



İlk önce dDeger
değişkenin tanımlanma ve atanma kısımlarının ayrı olduğuna dikakt
etmişsinizdir. Eğer dDeger değişkeni try bloğunda tanımlansaydı
try bloğunun dışındaki herhangi bir yerde geçerli olmayacak ve
catch bloğunda bu değişkene atıfta bulunulamayacaktı.



Eğer Double.Parse
metodu girilen string’i double’a çevirme işleminde başarılı olursa
program catch bloğundan sonra takip edilen ifadeden çalışmaya
devam edecekti. Eğer Parse metodu bir hata fırlatırsa catch bloğu
onu yakalar. catch bloğundaki ifade işletilir ve bundan sonra
programın normal işletimi catch bloğunu takip eden ifadeden
işletilmeye devam eder. Dikkat edersek catch bloğu bir mesaj
gösterir ve dDeger değişkenine NaN değerini atar. Muhtemelen catch
bloğunu takip eden kod dDeger değişkenini NaN değerine eşitler ve
kullanıccan tekrar bir değer girmesini ister.



Kullanıcıdan nümerik
değerler okuyan bir programda muhtemelen tyr ve catch bloklarını
parse metodunu geçene kadar kullanıcıdan değerleri tekrar
girmesini isteyen bir do döngüsünün içine koyarız. Aşağıda tam
olarak bunu yapan bir metod içeren bir program görüyorsunuz.





using System;

class DoubleGiris
{
static void Main()
{
double dTaban = DoubleSayiAl("Taban sayıyı Gir:");
double dUst = DoubleSayiAl("Üst sayıyı gir:");
Console.WriteLine("{0} 'nin {1}. kuvveti = {2}",dTaban,dUst,Math.Pow(dUst,dTaban));
Console.ReadLine();

}

static double DoubleSayiAl(string ifade)
{
double dDeger = Double.NaN;

do
{
Console.Write(ifade);
try
{
dDeger = Double.Parse(Console.ReadLine());
}
catch
{
Console.WriteLine();
Console.WriteLine("Geçersiz bir sayı girdiniz");
Console.WriteLine("Lütfen Tekrar Giriniz");
Console.WriteLine();
}
}
while( Double.IsNaN(dDeger) );
return dDeger;

}


}





DoubleSayiAl metodu bir string parametresi alıyor ve bir double
değer döndürüyor. Metot dDeger değişkenini tanımlayarak ve NaN
degeri atayarak başlıyor. do döngüsü bunu takip ediyor. string
parametresini komut gibi görüntüleyerek başlıyor ve girilen string
değeri bir double değere çevirme denemeleri yapıyor.



Eğer bir istisna
meydana gelirse metot kullanıcıya bir mesaj gösteriyor. do
döngüsünün sonunda Double.IsNaN statik metodu true değeri
döndürür, bu nedenle programın işletimi do döngüsünün başına
dönerek oradan devam ediyor ve komutlar tekrar görüntüleniyor.



Eğer parse başarılı bir
şekilde değer döndürürse programın işletimi catch bloğundan sonra
gelen ifadelerden devam eder. Double.IsNaN metodu false değeri
döndürür ve DobuleSayiAl metodu dDeger değişkenini main metoda
döndürür.



Tabiî ki DoubleSayiBul
metodu birçok basit Parse çağrısı yapıyor. Bu main metodu daha
basit hale getirmek adına ödenmiş bir bedel. Ve tabiî ki
kullanıcının programın pike yaptığını görme fırsatı olmuyor.



Parse ifadesinin hata
fırlattığında normalde yaptığı işleri yapmayacağını fark etmek
önemli. Programın işletimi parse metodunun derinlerine bir
yerlerden catch ifadesine doğru devam eder. Eğer dDeger
değişkenini NaN ile ilişkilendirmezsen catch ifadesi işletilirken
dDeger ilişkilendirilmemiş olacak.



double yerine decimal
tipteki değerlerle çalışıyorsan, geçerli bir değer girilemediğinde
değişkeni NaN değerine eşitlemek zorunda değilsiniz. Bunun yerine
kullanıcının bir sayı değeri girmeye fırsatı olmadığını varsayarak
decimal değişkeni mümkün olan minimum değerle ilişkilendirmelisin
(Decimal.MinValue) ve bu minimum değeri karşılaştırmalısınız.





while ( mDeger != Decimal.MinValue )





Yada adı bGecerliDegeriAl olan ve false değeri alan sadece try
bloğunun içinde Parse metodunun çağrısından sonra true değeri alan
bir boolean değişkenle ilişkilendirmelisiniz.





try
{
dValue = Double.Parse(Console.ReadLine() );
bGecerliDegeriAl = true;
}

Sonra da do döngüsünün sonunda bu değeri kullanırsın

while ( ! bGecerliDegeriAl ) ;





DoubleGiriş programının içinde kullanılan catch ifadesi genel
catch ifadesi olarak bilinir. try bloğunun içinde herhangi bir
istisna meydana gelirse bu ifade yakalayacak. Bunun yerine özel
bir tür istisna için bir catch ifadesi belirleyebilirsiniz.





try
{
// denenecek durum ve ya durumlar
}
Catch ( Exception exc )
{
//hata işlemi
}





catch anahtar kelimesini bir metodun parametrelerine benzeyen
parantezler bir değişken tanımlaması takip ediyor. Exception
System isimuzayı (namespace) içinde tanımlanmış bir sınıf ve exc
de ( istediğiniz herhangi bir başka ismide verebilirsiniz )
Exception tipinde bir nesne olmak için tanımlanmış. catch bloğu
ile bu Exception nesnesini kullanarak hata hakkında daha fazla
bilgi elde edebilirsiniz. Aşağıdaki örnekteki gibi Message
özelliğini kullanabilirsiniz.





Console.WriteLine(exc.Message);



Bu yazının başında
gösterilen programda bu Message özelliği kullanılırsa aşağıdaki
gibi bi uyarı olacaktı.



Girilen String doğru
formatta değil



Eğer kullanıcı örneğin
numaralar yerine harf girerse uyarı aşağıdaki gibi olacaktı.



Değer Double için çok
küçük veya çok büyük



Bu hata eğer bilimsel
gösterimlerdeki gibi çok büyük veya çok küçük sayılar girildiğinde
ortaya çıkabilir. Bunlar gibi kendiniz mesaj yazmak yerine bu
Message özelliğini kullanarak mesajlar yazdırabilirsiniz.



Eğer WriteLine metoduna
Exception nesnesini doğrudan gönderirseniz







Console.WriteLine(exc.Message);



gibi




etkin olarak ToString
metodunu çağırabilirsiniz. O zaman bütün bir detaylı istisna
mesajını görebilirsiniz.





Console.WriteLine(exc.ToString());



Her ne kadar catch
bloğu Exception nesnesi ile özel catch ifadesi olarak
sınıflandırılsa da, bilinen catch ifadesinin genelleştirilmişidir.
Çünkü bütün farklı istisna sınıfları (Sınıfra bölünebilme hatası,
Taşma hatası, ve diğerleri ) Exceprion sınıfının başında bir
hiyerarşi ile tanımlandı. Diğer bütün istisnalar Exception
sınıfından miras alındı. Diğer makalelerde miras hakkında daha
fazla bilgi bulabilirsiniz. Şimdilik herhangi bir tip istisnayı
temsil eden Exception sınıfını düşünebilirsin. Diğer sınıflar
Exception sınıfından alınmış daha özel miraslardır.



Double sınıfmın Parse
metodu üç istisnadan birini ortaya çıkarabilir: FormatException
(harf yazıldığında), OverflowException (Sayı çok büyük veya çok
küçük olduğunda), veya ArgumentNullException (Parse edilen argüman
Null Olduğunda). Aşağıda bu üç özel istisnanın üstesinden gelmek
için verilmiş bir örnek var.







try
{
dDeger = Double.Parse(Console.ReadLine());
}
catch (FormatException exc)
{
//FormatException hatası ile ilgili kodlar
}
catch (OverflowException exc)
{
//OverFlowException hatası ile ilgili kodlar
}
catch (ArgumentNullException exc)
{
//ArgumentNullException hatası ile ilgili kodlar
}
catch (Exception exc)
{
//Diğer bütün hatalar ile ilgili kodlar
}





Catch ifadeleri hatalarla eşleşme durumlarına göre birincisinden
itibaren sıra ile incelendi. Son catch ifadesi hiçbir parametresi
olmayan genel bir ifade olabilir. Aynı şekilde eğer özel tip
istisnalar ile çalışıyorsan her zaman diğer özel bir şekilde
ilgilenilmeyen istisnalar içinde genel bir catch ifadesi
yazmalısınız.



Yukarıdaki özel örnekte
Double.Parse metodunu kullandığımızda en son catch bloğu hiçbir
zaman işletilmeyecek. Çünkü metodundan doğabilecek olan her
istisna ile özel olarak halledildi. Üstelik ArgumentNullException
hatası bile hiçbir zaman ortaya çıkmayacak çünkü Console.ReadLine
metodundan asla Null değeri dönmez. Fonksiyonel olmayan catch
ifadelerinden zarar gelmez.



try ifadesinin içinde
kullanabileceğin finally olarak cağırılan üçüncü bir ifade daha
var. finally ifadesi catch ifadelerinden sonra aşağıda
gösterildiği şekli ile gelir.





finally
{
// finally bloğu içerisindeki ifadeler
}



finally bloğunun
içindeki ifadeler try ifadesinden sonra ( eğer istisna
fırlatılmadıysa ) veya catch ifadesinden sonra çalışması garanti
edilmiş ifadelerdir. Finally ifadesi ilk karşılaştığınızda karışık
gelebilir. Böyle bir şey neden gerekli sorusu akla gelebilir. Eğer
try bloğunda bir istisna meydana gelmediyse programın işletimi
catch bloğundan sonraki ifadeden normal bir şekilde devam edecek.
Eğer bir istisna meydana gelirse program catch bloğuna gidecek ve
ondan sonra catch bloğundan sonra gelen ifadelerden devam edecek.
Eğer try ve catch bloklarından sonra iletilecek herhangi bir kod
yazmak istiyorsam neden bu kodu catch bloğundan sonraya
yerleştirmiyorum? Neden finally bloğunu kullanıyorum diye
düşünebilirsiniz.



Bu karmaşıklığın çözümü
basit: try ve catch blokları bir controle dönmek için bir return
ifadesi içerebilir veya (eğer metot Main metot ise ) programı
sonladırablir. Bu durumda try ifdesnin en sonundaki catch ifadesi
işletilemeyecek. İşte finally ifadesi bize burada yardımcı oluyor.
Eğer try ya catch ifadeleri bir return içeriyorsa her dururumda da
finally bloğunun çalışması garanti ediliyor.



Genellikle finally
ifadesi temizlik yapmak için kullanabilirsin. Örneğin eğer
programında bir dosya yazarken hata meydana geldiyse finally bloğu
programı herhangi bir tamamlanmamış durumda bırakmamak için
dosyayı kapatabilir ve silebilir.



catch ifdesi olmadan da
bir finally ifadesi kullanabilirsin. try ifadesinin üç farklı
kullanım şeklini aşağıda görüyoruz.



• try bloğu, bir veya
birdan fazla catch bloğu

• try bloğu, bir veya birdan fazla catch bloğu, finally bloğu

• try bloğu, finally bloğu



Son yapılandırma da
program her hangi bir hata yakalamaz. Kullanıcı bir mesajla
bilgilendirilir ve program sonlandırılır. Fakat program
sonlandırılmadan finally bloğu çalıştırılır.



Şimdi hataların nasıl
yakalandığını biliyorsun. Nasıl fırlatıldığını da bilmelisin. Eğer
çeşitli sıralamalar yapan ve hata meydana getirebilecek bir metot
yazıyorsan, genel olarak metodun problemleri meydana getirecek
olan kodlar çağırıyor olduğunu fark etmesi için bir hata
fırlatmasını istersin. throw ifadesi aşağıdaki kadar basit
olabilir.





throw;



fakat throw ifadesinin
bu basit formu istisnayı tekrar fırlatması için catch bloğunun
içinde kullanılır.



Eğer bir istisnayı
tekrar fırlatmıyorsan, throw ifadesinin içine bir argüman
yerleştirmelisin. Bu argüman Exception sınıfının bir örneği veya
kendi oluşturduğun indirgenmiş bir sınıfıdır.





throw new Exceptinon();



new deyimi Exception
sınıfının bir örneği olan bir yapıcıyı içerir. Fakat gerçektende
throw değiminde Ecception sınıfının bir örneğini kullanamazsın,
çünkü o sana ne çeşit bir hata olduğunu söylemez. Bir veya birden
fazla özel istisna kullanmalısınız. Çok sıklıkla System
isimuzayının içinde senin ihtiyacın olan ne ise ona daha yakın
olan indirgenmiş istisnalar bulacaksınız. Örneğin senin metodun
bir string parametre içeriyorsa ve null bir argüman
gönderildiğinde çalışmıyorsa muhtemelen aşağıdaki gibi bir kod
yazarsınız.





İf ( strGir == null )
Throw new ArgumentNullException;



Bu
ArgumentNullException yapıcısına bir string argüman da
gönderebilir. Argüman probleme neden olan özel bir metot
parametresini gösterebilir:





if (strgir == null )
Throw new ArgumentNullException(“Giriş stringi”);



Yapıcıya gönderdiğin bu
string bu istisna ile uğraşan catch ifadesine uygun olan istisna
mesajının bir parçasına dönüşecek.



throw ifadesinin
işletiminden sonra metodun bittiğini tekrar vurgulayalım. Bundan
sonraki kodlar işletilmeyecek. Bu if den sonraki bir else
ifadesinin bir anlam ifade etmeyeceğini gösteriyor.



if (strgir == null )
Throw new ArgumentNullException(“Giriş stringi”); else { //istisna
fırlatılmazsa bir şeyler yap }



}



Throw ifadesi içeren
bir if ifadesinden sonra diğer kodları basitçe devamına
yazabilirsiniz.





if (strgir == null )
Throw new ArgumentNullException(“Giriş stringi”);

//istisna fırlatılmazsa bir şeyler yap





interger’lar için bir Parse metodu yazalım. Nispeten basit olması
için, negatif değerlere izin verilemesin. Normal Parse metodu gibi
MyParse metodu üç tip istisna fırlasın: Eğer argüman null ise
ArgumentNullException, eğer girilen değerler satır değil de
harfler içeriyorsa FormatException veya eğer bir interger için
uygun olmayan sayılar girilmişse OverflowException.





using System;

class HataFirlatma
{
static void Main()
{
int iGir;

Console.WriteLine("İşaretsiz bir sayı gir:");

try
{
iGir = MyParse(Console.ReadLine());
Console.WriteLine("girilen Deger:{0}", iGir);
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
}
static int MyParse(string str)
{
int iSonuc = 0, i = 0;

//eğer argüman null ise bir hata fırlat
if( str == null )
throw new ArgumentNullException();

//girilen degerde boşluklar varsa onları temile
str = str.Trim();

//en az bir karakter girilip girilemdiğini kontrol et
if ( str.Length == 0 )
throw new FormatException();

//stirngin içindeki bütün karakterleri dön
while ( i < str.Length )
{
//eğer karakterler sayı değilse bir hafa fırlat

if( !Char.IsDigit(str,i) )
throw new FormatException();

//bir sonraki haneyi hesapla ( "chacked" e dikkat et)
iSonuc = checked(10 * iSonuc + (int)str[i] - (int)'0');

i++;
}
return iSonuc;
}
}



MyParse metodu ilk önce
trim metodu ile girilen değerin başındaki ve sonundaki whitespace
denilen boşlukları siliyor. Sonra while döngüsünü kullanarak
girilen stirng içindeki bütün karakterleri dönüyor. Eğer karakter
IsDigit testini geçerse metot iSonuc değerini 10 ile çarpıyor ve
Unicode den nümerik bir değere çevrilmiş yeni bir basamak ile
topluyor. MyParse metodu açıkça bir OverflowException fırlatmıyor.
Onun yerine normal bir taşma hatası üretmek için chacked
ifadesinde bir hesaplama icra ediyor. Main metot MpParse metodu
ile fırlatılan bir hatayı yakalamayı tecrübe etmenizi sağlıyor.
Hanci.org sizlere daha iyi hizmet sunmak için çerezleri kullanıyor.
Hanci.org sitesini kullanarak çerez politikamızı kabul etmiş olacaksınız.
Detaylı bilgi almak için Gizlilik ve Çerez Politikası metnimizi inceleyebilirsiniz.