C# 6.0 – İstisna Filtreleri

   İstisna Filtreleri (Exception Filters), bence C# 6 ile birlikte gelen önemli, bir o kadar da dikkatle yaklaşılması gereken bir yenilik. Alışa geldiğimiz try-catch bloklarında yakalanan istisnanın hangi iş mantığı bloğunca işleneceği tek bir kritere bağlıdır; istisnanın türü. C# 6 ile birlikte bu mantık bir adım ileri taşınarak ek bir kontrol cümleciği ile daha iyi bir istisna yakalama mekanizması kuruluyor. C# için bu bir yenilik olsa da VB ve F# buna uzun zamandır sahipti.

   Aşağıda hali hazırda kullandığımız try-catch bloğuna bir örnek bulabilirsiniz;

try { 
    //İş mantığı
} catch (YakalamakIstedigimizException e) {
    //İstisna yakalama iş mantığı
}

   Örneğimize finally bloğu ya da yeni catch blokları ekleyebiliriz. İstisna filteleri ile birlikte catch’in söz diziminde değişliğe gidilerek koşul konulabilmesi adına if bloğu eklenmiştir. Buna göre, aynı try-catch bloğunda artık aşağıdaki şekilde filtreleme yapabiliriz;

try { 
    //İş mantığı
} catch (YakalamakIstedigimizException e) if (IstisnaKosulu(e)) {
    //İstisna yakalama iş mantığı
} 
private static bool IstisnaKosulu(Exception ex) {
    //Filtreleme iş mantığı
}

   Örneğimizde yer alan if bloğu daha önce kullandığımız if bloğundan farksız.

try { 
    //İş mantığı
    throw new Exception();
} catch (YakalamakIstedigimizException e) if (IstisnaKosulu(e)) {
    //İstisna yakalama iş mantığı
} 

   Yukarıdaki örnekte iş mantığımız içerisinde bir Exception atılmaktadır. Bu durumda CLR öncelikle daha önceden yaptığı gibi catch blokları arasında bu türü yakalayabilecek bir blok olup olmadığını kontrol edecektir. Örneğimizde uygun blok olmadığı için istisna yönetilemeyecektir. Diğer yandan aşağıdaki örneğimizi ele alalım;

try { 
    //İş mantığı
    throw new YakalamakIstedigimizException();
} catch (YakalamakIstedigimizException e) if (IstisnaKosulu(e)) {
    //İstisna yakalama iş mantığı
} 

   Bu örnekte, fırlatılan YakalamakIstedigimizException istisnası yakalayabilecek bir blok bulunmaktadır. Bu durumda CLR blok için bir koşul olup olmadığına bakacaktır. Koşul olması durumunda koşul bloğu işletilerek istisnanın catch bloğunca yönetilik yönetilmeyeceğine karar verecektir.

try { 
    //İş mantığı
    throw new YakalamakIstedigimizException();
} catch (YakalamakIstedigimizException e) if (IstisnaKosulu1(e)) {
    //İstisna yakalama iş mantığı-1
} catch (YakalamakIstedigimizException e) if (IstisnaKosulu2(e)) {
    //İstisna yakalama iş mantığı-2
} 

   Dikkat ederseniz yukarıda paylaştığım örnekte her catch bloğuda aynı tür için işlem yapmakta; fakat farklı koşullara sahip. CLR tarafından işletilen istisna filtrelemesi daha önceden de olduğu gibi yukarıdan aşağıya doğru yapılacaktır. İlk uygun bloğun bulunması durumunda daha fazla arama yapılmayarak bu blok işletilecektir.

   İstisna filtreleri ile birlikte en güzel kazanımımız stack trace’i kaybetmemek. Ok, aslında kazanım demek doğru olmayacaktır; çünkü önceden de throw kullanarak kaybetmeyebiliyorduk 😉 Son örneğimize geri dönelim. Aynı örneği istisna filtreleme kullanmadan aşağıdaki şekilde yazabilirdik;

try { 
    //İş mantığı
    throw new YakalamakIstedigimizException();
} catch (YakalamakIstedigimizException e) {
    if (IstisnaKosulu1(e)) {
        //İstisna yakalama iş mantığı-1
    }else if (IstisnaKosulu2(e)) {
        //İstisna yakalama iş mantığı-2
    }else{
        throw ex;
    }
} 

   Bu kod ile ilgili en büyük problem her iki koşula da uymayan istisnalarda throw ex satırı ile birlikte istisnaya dair stack trace’i kaybetmemizdi. Bu da istisnayı çözmeye çalışırken bize zaman kaybettirecektir. Tabi biraz daha deneyimli  bir yazılımcı iseniz bu tuzağa düşmeyip kodu şu şekilde yazardınız;

try { 
    //İş mantığı
    throw new YakalamakIstedigimizException();
} catch (YakalamakIstedigimizException e) {
    if (IstisnaKosulu1(e)) {
        //İstisna yakalama iş mantığı-1
    }else if (IstisnaKosulu2(e)) {
        //İstisna yakalama iş mantığı-2
    }else{
        throw;
    }
}

   Bu sayede yönetemediğiniz istisnalarda stack trace’i yok etmemiş olurdunuz. İşte istisna filtreleme ile gelen kazanımlardan birisi de bu. Junior bir yazılımcı da artık farkında olmadan böylesi bir kullanıma yönlendiriliyor, bu şekilde bir kod çıkmış oluyor 😉

   Bu şekildeki bir kod iyileştirmesinin yanında istisna filtrelerinin daha “ulvi” amaçlar içinde kullanıma açık olduğunu söylemeden geçemeyeceğim. baktığınız zaman en nihayetinde istisna oluştuğunda CLR ilgili catch bloğunun kullanılıp kullanılmayacağına karar vermek için öncelikle if bloğunu işletecektir. Bu bakış açısı ile aşağıdaki kod parçacığını inceleyelim;

try { 
    //İş mantığı
    throw new YakalamakIstedigimizException();
} catch (Exception e) if (SadeceIstisnayiLogla(e)) {
    //Burası hiç bir zaman işletilmeyecek...
} catch (YakalamakIstedigimizException e) if (IstisnaKosulu1(e)) {
    //İstisna yakalama iş mantığı-1
} 
catch (YakalamakIstedigimizException e) if (IstisnaKosulu2(e)) {
    //İstisna yakalama iş mantığı-2
} 

private static bool SadeceIstisnayiLogla(Exception ex) {
    //Sadece oluşan istisnayı logla
    
    return false;
}

private static bool IstisnaKosulu1(Exception ex) {
    //Filtreleme iş mantığı
}

private static bool IstisnaKosulu2(Exception ex) {
    //Filtreleme iş mantığı
}

   Dikkat ederseniz örneğimizde ilk catch bloğunca kullanılan koşul fonksiyonumuz SadeceIstisnayiLogla her zaman için false değer dönmekte. Bunun anlamı bu catch bloğu hiç bir zaman işletilmeyecek. Öte yandan CLR buna karar vermek için her zaman SadeceIstisnayiLogla fonksiyonunu çalıştıracak. Bloğumuzun catch blokları arasında ilk sırada olması nedeniyle her istisnada mutlaka istisnayı yönetmekte kullanılıp kullanılmayacağı kontrol edilecek. Bu durumda yazılım geliştirici olarak SadeceIstisnayiLogla fonksiyonu içerisine istisnaya dair loglama işlemlerini koyabilirim. Fonksiyonum try-catch bloğundaki yönetilen/yönetilmeyen bütün istisnalar için çalıştırılacak ve bu işlemlerde stack trace hiç bir şekilde etkilenmeyecek… Harika değil mi!!

 

   Makalemin şimdiye kadar ki bölümlerinde size İstisna Filtrelerinin hep güzel taraflarını paylaştım, kabul ediyorum. Şimdiii, sıra geldi sıkıntı oluşturabilecek yanlarına… Hatırlarsanız yazımın daha hemen başında bu özelliğin önceden beri VB ve F#’ta bulunduğundan bahsetmiştim. Bu özelliğin C# geç gelmesinin elbette mantıklı bir nedeni var 😉

   Baştan belirtmeliyim; istisna filtreleri güçlü olduğu kadar tehlikeli de olabilir… Aşağıdaki konsol uygulamamızı inceleyip konsola ne çıktı vereceğini tahmin etmeye çalışın;

using System;

namespace Com.Enterprisecoding.IstisnaFiltresi {
    class Program {
        static void Main(string[] args) {
            try {
                try {
                    Console.Write("A");

                    throw new Exception();
                }
                finally {
                    Console.Write("B");
                }
            }
            catch (Exception ex) if (IstisnaKosulu(ex)){
                Console.Write("C");
            }
            catch (Exception) {
                Console.Write("D");
            }

            Console.Write("F");
        }

        public static bool IstisnaKosulu(Exception ex) {
            Console.Write("E");
            return true;
        }
    }
}

   Uygulamayı Visual Studio‘da denemediğinizi umuyorum… Ardıl harfleri kullanmam nedeniyle eminim ki ABCDEF yanıtı verenleriniz olmuştur. Kabul ediyorum, ardıl harfler yanıltmacaydı. En şanslı tahmininiz ABECF olabilir mi? Hayır o da değil! Doğru yanıt AEBCF.   Şaşırdınız değil mi! finally bloğundan önce çalışan ve dıkapsamda yer alan bir istisna filtresi! Evet doğru… C#’ta alışkın olmadığımız şekilde iç içe geçmiş bir kapsam (scope) yönetimi!

   Uygulamanın içerdeki finally bloğunu çalıştırmadan önce dışardaki istisna filtresini çalıştırması tamamen CLR’ın istisna yönetim mantığına dayanmakta. Konunun daha da derinine inecek olursak CLR’in istisna yönetim mantığı da Windows’un istisna yönetimi ile uyumlu çalışması gereksinimden doğmakta. Windows’un istisna yönetimi konusunda bilgi sahibi olmayanlarınız için kesinlikle daha önceden paylaştığım bu makaleyi okumanızı ya da Enterprisecoding TV’de yayınlanan bu videoyu izlemenizi tavsiye ederim.

   CLR istisna yönetimi iki fazlı bir işlem gerçekleştirir. Bir istisna oluştuğu zaman ilk fazda bu istisnayı yönetebilecek bir blok olup olmadığı analiz edilir. Bu fazdaki amaç uygulamanın istisnayı yönetip yönetmediğinin ortaya çıkması olduğu için sadece catch blokları incelenir. finally blokları henüz bir işlem gerçekleştirilmeyeceği için göz ardı edilir. Hata yönetim bloklarının tespiti ardından ikinci faz’a geçilir. İkinci faz artık istisnanın yönetimi fazıdır. finally ve tespit edilen catch blokları gerçekte bu fazda çalıştırılacaktır.

   Bu süreç şimdiye kadar kullandığımız try-catch bloklarında sıkıntısız işlemekteydi. CLR yine karşılaştırma yapıyordu; fakat biz yazılım geliştiricilerin yazdığı kod parçacıkları olmadan, sadece catch bloklarında belirttiğimiz türler karşılaştırılarak. Bu durumda süreç aynı olmasına rağmen yukarıdaki gibi bir durumla karşılaşmıyorduk. Durum işin içine istisna filtrelemesinin girmesiyle değişti. İstisna filtreleri ile birlikte CLR ilk fazda sadece tür karşılaştırması değil, aynı zamanda bir if bloğu da çalıştırmak zorunda. Yani, örneğimizde olduğu gibi, alt kapsam henüz sonuçlanmadan üst kapsama ait kod parçacıkları çalışabiliyor.

  CLR’ın istisna yönetimindeki bu yaklaşımı yazılımcıların daha dikkatli davranmasını gerektiriyor. Finally bloklarının birincil amacının try-catch bloğu içerisinde kullanılan ve istisna nedeniyle düzgün şekilde iade edilememiş sistem kaynaklarının doğru şekilde teslim edilmesi olduğunun altını çiziyorum. finally bloğundan önce sahne alan istisna filtrelerinin çalışması sırasında bu sistem kaynakları doğru statülerde ve sağlıklı durumda olmayabilir. Bu bakış açısıyla, istisna filtrelerinde try-catch bloklarında bulunan kaynakların kullanılmaması daha doğru bir yaklaşım olacaktır.

   Bu konuda dikkatinizi çekmek istediğim bir nokta da using blokları olacak. Bildiğiniz gibi using blokları, bir sistem kaynağının blok ile belirtilen kapsam sonlandığından sisteme iade edilmesi mantığına dayalıdır. Derleyici tarafından üretilen kodlar incelenecek olursa using bloklarının da try-catch üzerine kurgulandığı görülecektir. Bu bilgiler ışığında yukarıda paylaştığım kurgu using blokları için de geçerli olacağı için aynı hassasiyetin using blokları için de gösterilmesi önemlidir.

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir