Öznitelik’ler ve Reflection’la C# Dilini İhtiyaçlarınız Doğrultusunda Genişletin

   C# anlatırken ne zaman konu özniteliklere (attributes) gelse, bu konuyu ilk dinleyenler için konu hep havada kalır. “Kodunuzun içerisine ekleyebileceğim meta veriler mi!?”, “Bu meta veriler ne işime yarayacak ki?” Bu sorulara verdiğim yanıt ise biraz daha sabırlı olmaları ve hemen ardından anlattığım reflection konusunu dinlemeleri oluyor.

   Öznitelikler ve Reflection bir araya geldiğinde C# dilini gerçekten de oldukça güçlü kılıyor. Öyle ki; bu ikisini kullanarak pek çok noktada dil’e yeni bir ekleme yapma, yeni bir anahtar kelime ihtiyacı ortadan kalkıyor. System kütüphanesi altında yeralan SerializableAttribute özniteliğini ele alalım örneğin; .Net’in bir sınıfın serileştirilebilip serileştirilemeyeceğine karar vermesini sağlayan bu öznitelik sayesinde kendi sınıfımızın serileştirme sırasındaki davranışını kontrol edebiliyoruz. Bunun içinde tek yaptığımız SerializableAttribute özniteliğini sınıfımızın üzerine ekleme (ya da eklememek). Bu işlevi öznitelikler kullanmadan nasıl yapardık peki? C#’ın bir gün bizim işimizi görecek bu tarz bir işlev kazanmasını, yani yeni bir anahtar kelime eklemesini, beklerdik.

   Konuyu daha fazla dallandırmadan önce, isterseniz konuya yabancı olanlarınız için öznitelik ve reflection kavramlarına kısaca değinelim;

Öznitelikler (Attributes)

   Bir .Net uygulaması kod, veri ve meta veri temel bileşenlerinden oluşur. En basit anlatımıyla, C# öznitelikleri türler, kod, assembly, v.b. C# bileşenlerine yüklenilen meta verilerdir. Temelde bu meta veriler programınızın içerisine gömülmektedir ve ILDasm ya da benzeri meta veri görüntüleyicilerince görülebilir durumdadırlar.

  Tanımlanmış/tanımlanacak tüm öznitelikler abstract olan Attribute sınıfından türemek zorundadır. İstenirse, tanımlanmaları sırasında, kullanılabileceği alanları da (örneğin; bu öznitelik yalnızca sınıflar için ya da yalnızca metodlar için kullanılabilir) belirtebilirsiniz.

    Aşağıda öznitelikler için verilebilecek kullanım alanlarını bulabilirsiniz;

All Özniteliğin şağıda sıralanan tüm bileşenlerde kullanılabileceğini belirtir
Assembly Özniteliğin assembly’nin kendisi için kullanılabileceğini belirtir.
Class Özniteliğin sınıf için kullanılabileceğini belirtir
ClassMember Özniteliğin sınıf, struct, enum, constructer, metod, özellik (property), alan (field), olay (event), temsilci (delegate) ve arayüzler için kullanılabileceğini belirtir
Constructer Özniteliğin constructer’lar için kullanılabileceğini belirtir
Delegate Özniteliğin temsilciler için  kullanılabileceğini belirtir
Enum Özniteliğin enumlar için kullanılabileceğini belirtir
Event Özniteliğin olaylar için kullanılabileceğini belirtir
Field Özniteliğin alanlar için kullanılabileceğini belirtir
Interface Özniteliğin arayüzler için kullanılabileceğini belirtir
Method Özniteliğin metodlar için kullanılabileceğini belirtir
Module Özniteliğin moduller için kullanılabileceğini belirtir
Parameter Özniteliğin metod parametreleri için kullanılabileceğini belirtir
Property Özniteliğin özellikler için kullanılabileceğini belirtir
ReturnValue Özniteliğin metodların dönüş değerleri için kullanılabileceğini belirtir
Struct Özniteliğin struct’lar için kullanılabileceğini belirtir

   İş mantığı doğrultusunda birden çok kullanım alanının belirtilebilmesi de mümkündür.

   Aşağıda örnek bir öznitelik tanımlamasını bulabilirsiniz;

[AttributeUsage(
AttributeTargets.Class, 
AllowMultiple = false, 
Inherited = false)]
public class OrnekAttribute : Attribute { }

  Bu örnekte, tanımladığımız özniteliğin ne şekilde kullanılması gerektiği bilgisini 1. ve 4. satırlar arasında yer alan AttributeUsage özel özniteliği vasıtasıyla derleyiciye belirtmekteyiz. 2. satırda yer alan AttributeTargets.Class parametresi yukarıda vermiş olduğum tablodan da görüleceği gibi OrnekAttribute özniteliğimizin sadece sınıf tanımlamalarında kullanılabileceğini belirtmekte. 3. satırda derleyiciye bir sınıf üzerinde bu öznitelikten aynı anda sadece bir tane kullanımasına izin verildiğini belirtmekte. 4. satır ise bu özniteliğin ata sınıflarda bulunsa bile tanımlanmadığı sürece kalıtılan sınıflarda geçerli olmayacağını (başka bir deyişle tanımsız olacağını) belirtmektedir.

   Yıkarıda gördüğünüz basit öznitelik tanımlaması dışında istenirse parametreler ve özellikler (property) vasıtasıyla yazılım geliştiriciden ek bilgi alan özniteliklerde tasarlanabilir. Temelde bakılacak olursa öznitelikler’de birer sınıftırlar, dolayısıyla bir sınıf ile yapabileceğiniz her şeyi (varsayılan ya da parametreli constructer, özellikler, hatta metod ve arayüz kullanımı) yapmanız mümkün.

   Öznitelikleri anlatırken tam da bu noktada akıllara yukarıda saydığım sorular geliyor, örneğin : “Bu meta veriler ne işime yarayacak ki?”; yanıtına gelmeden önce bir de reflection nedir inceleyelim.

Yansıma (Reflection)

   Programsal olarak bir programa eklenmiş meta verilerin okunması, programın kendi yansımasına bakması, yönetimi kısaca yansıma (reflection) olarak adlandırılmaktadır. Reflection yöntemiyle edinilen bu bilgiler kullanıcıya gösterilebileceği gibi programın iş mantığı içerisinde de kullanılabilir. Başka bir deyişle, programa derleme sırasında eklediğimiz meta verileri çalışma zamanında yorumlarayarak iş akışımıza yön verebiliriz.

   Reflection konusunda değinmem gereken bir önemli noktada, derleme zamanında sadece sizin belirlediğiniz özniteliklerin programa eklenmediğidir. Reflection yöntemiyle isterseniz .Net tarafından sizlere sunulan tür bilgilerini de kullanabilirsiniz. İstediğiniz bir türün metodları, özellikleri, alanları v.b. bilgileri de çalışma zamanında reflection ile bulunabilirsiniz. Hatta, bu bileşenlere yüklenmiş öznitelikler bile bulunabilirsiniz.

   Konuyu pekiştirmek adına elimizde aşağıdaki gibi bir öğrenci sınıfının olduğunu varsayalım;

public class Ogrenci {
    public string Adi;
    public string Bolum;
}

   Aşağıdaki kod parçacığı ile Ogrenci sınıfımız içerisindeki tüm alanları ekrana yazabiliriz;

Type ogrenciTuru = typeof(Ogrenci);
FieldInfo[] ogrencialanlari = ogrenciTuru.GetFields(
                                             BindingFlags.Public |
                                             BindingFlags.Instance);

foreach (FieldInfo ogrenciAlani in ogrencialanlari) {
    Console.WriteLine("Alan :" + ogrenciAlani.Name);
}

   Bu kod parçacığında; ilk satırda öğrenci türüne ulaşıyoruz. Daha sonra ikinci satırda Type sınıfı içerisinde yer alan GetFields metodunu kullanarak bu tür içerisindeki alan bilgilerine ulaşıyoruz. GetFields metoduna verdiğimiz BindingFlags parametreleri ise (3. ve 4. satırlar) public olarak tanımlanmış ve static olmayan alanların listelenmesini istediğimizi belirtiyoruz. Bu parametre ile oynayarak farklı kombinasyonlarla değişik özellikteki alanları da listeletmek mümkün. Kod parçacığımızın devamında ise bu alanların isimlerini konsola yazdırıyoruz. ogrenciTuru’nü kullanarak türe ait metodları, özellikleri hatta öznitelikleri bulabilmemiz mümkün.

   Gördüğünüz gibi reflection yöntemini kullanarak çalışma zamanında türler hakkında bilgi edinmek oldukça kolay. Bir adım ötesinde ise aşağıdaki kod parçacığında olduğu gibi alanların değerlerini almakta mümkün :

var ogrenci = new Ogrenci {
    Adi = "Fatih Boy",
    Bolum = "Bilgisayar Mühendisliği"
};

Type ogrenciTuru = typeof(Ogrenci);
FieldInfo[] ogrencialanlari = ogrenciTuru.GetFields(
                                    BindingFlags.Public |
                                    BindingFlags.Instance);

foreach (FieldInfo ogrenciAlani in ogrencialanlari) {
    Console.WriteLine("Alan :" + ogrenciAlani.Name);
    Console.WriteLine("Değer :" + ogrenciAlani.GetValue(ogrenci));
    Console.WriteLine("******************");
}

   13. satırda görüldüğü gibi yapılması gereken aldığımız alan bilgisini kullanarak ogrenci örneği için GetValue metodu ile değer sorgulamak.

Öznitelikler + Reflection = Esnek bir Dil

   Öznitelikler, reflection’la birleşince yapılabilecekler sadece hayal gücünüzle sınırlı bence. Örneğin; kullanıcıdan aldığımız bilgilerle doldurduğumuz Ogrenci sınıfında Adi alanının zorunlu olduğunu varsayalım. Programımız içerisinde bu alanın doğrulamasını yaparken aşağıdaki gibi bir kod yazarız;

if (string.IsNullOrEmpty(ogrenci.Adi)) {
    throw new Exception("Öğrenci adı boş olamaz!");
}

   Buradaki sıkıntı her alan ve her doğrulama yöntemi için bir kod eklenmesi gerekmesi. Doğrulanması gereken her yeni alan için buna benzer bir kodun eklenmesi gerekecektir. Genel geçer bir çözüm olmaması nedeniyle de bu yol bizim için her zaman için ek maliyet anlamına gelecektir. Keşke C# içerisinde zorunluluk kontrolü için bir anahtar kelime olsa dediğimiz yer tam bu ek maliyetlerle oldukça zaman kaybettiğimiz anlardır.

   Bu noktada aşağıdaki gibi bir kod yazdığınızı düşünün;

public class Ogrenci {
    [ZorunluAlan]
    public string Adi;

    public string Bolum;
}

   Bu örnek kodda görüldüğü gibi yeni bir anahtar kelimeye ihtiyaç kalmadan Adi alanını zorunlu olarak işaretleyebiliyorum. ZorunluAlan özniteliği tanımını aşağıda bulabilirsiniz;

[AttributeUsage(
    AttributeTargets.Field,
    AllowMultiple = false,
    Inherited = true)]
public class ZorunluAlanAttribute : Attribute { }

Zorunlu alan olarak işretledim; peki bu meta veriyi nasıl kullanabilirim? Cevap, tabiki reflection kullanarak;

public static class ZorunlulukKontrolu {
  public static bool Dogrula(object dogrulanacakOrnek) {
    Type dogrulamacakTur = dogrulanacakOrnek.GetType();

    FieldInfo[] dogrulanacakTurAlanlari = dogrulamacakTur.GetFields(
                                            BindingFlags.Public |
                                            BindingFlags.Instance);


    foreach (FieldInfo dogrulanacakTurAlani in dogrulanacakTurAlanlari) {
        object[] zorunluAlanOznitelikleri = dogrulanacakTurAlani.GetCustomAttributes(typeof(ZorunluAlanAttribute), true);

        if (zorunluAlanOznitelikleri.Length != 0) {
            string alanDegeri = dogrulanacakTurAlani.GetValue(dogrulanacakOrnek) as string;

            if (string.IsNullOrEmpty(alanDegeri)) {
                return false;
            }
        }
    }

    return true;
  }
}

   Aslında, ZorunlulukKontrolu sınıfı içerisindeki kodun büyük bir bölümü hakkında reflection konusunu anlatırken fikir sahibi olduğunuzu düşünüyorum : parametrenin tür bilgisi alınır (3.satır), içerisindeki public alanlar sorgulanır (5.,6.,7. satırlar) ve bulunan bu alanlar bir döngü içerisinde dönülür.

   Döngü içerisindeki kodda ise (11.satırdan itibaren); alan bilgisi üzerinden bu alan için tanımlanmış olan bir ZorunluAlanAttribute özniteliği olup olmadığı sorgulanır (11. satır). Eğer alan zorunluAlan olarak işaretlendi ise (13. satır) değeri bulunarak (14. satır) boş olup olmadığı kontrol edilir (15. satır), işaretlenmiş fakat değeri olmayan ilk alan bulunduğunda da metod false değeri ile döner (16. satır). Herhangi bir doğrulama hatası olmaması durumunda ise metod true değer döner (21.satır).

   Bu sınıfı programınız içerisinde aşağıdaki şekilde kullanabilir ve herhangi bir ogrenci örneğini doğrulayabilirsiniz.

if (!ZorunlulukKontrolu.Dogrula(ogrenci)) {
    throw new Exception("Öğrenci bilgileri doğrulamadan geçemedi!");
}

   Reflection ve öznitelikler kullanarak yazdığımız kod biraz daha uzun olabilir; ama bize sunduğu esneklik gerçekten de çok büyük. Örneğin; Ogrenci sınıfında Bolum alanı içinde zorunluluk kontrolü yapmak istersek eklememiz gereken tek bir satır olacaktır (aşağıdaki örnekte yer alan 5.satır);

public class Ogrenci {
    [ZorunluAlan]
    public string Adi;

    [ZorunluAlan]
    public string Bolum;
}

   Bu noktadan sonra sınıfımız içerisine ekleyeceğimiz tüm string alanlar için zorunluluk kontrolü yapmak tek bir satır eklemekle mümkün olabilecektir.

   Bu yöntemin bir başka güzel tarafı da; artık herhangi bir sınıf içerisindeki string türünden herhangi bir alan için zorunluluk kontrolü yapabiliyor olmanızdır. Örnek üzerinde biraz daha çalışılarak tüm veri türleri için çalışabilecek bir zorunluluk kontrolü yapılabilir. Hatta birden fazla öznitelik tanımlaması kullanılarak zorunluluk dışında format, değer aralığı, ileri tarih kontrolü v.b. kontrolleri yapabilir genel geçer doğrulama kodları oluşturulabilir.

   Gördüğünüz gibi, ihtiyacımız olan genel geçer bir zorunluluk kontrolü özelliğini C# dilinin yapısına dokunmadan, yeni bir anahtar kelime eklemeden, rahatlıkla yapabilmekteyiz. İşte bu yüzden öznitelikleri ve reflection’ı bu kadar çok seviyorum 🙂

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