Ana Sayfa

Bilgisayar Eğitim CD

Bilgisayar Kitapları

 
 
Adım Adım Web Eğitim Seti Adobe Flash Eğitim Seti


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.