Makale Özeti

Bu makalemizde, .NET Framework'ün üst veri(metadata) bilgilerine erişebilmek için kullandığı bir diğer yöntem olan System.ComponentModel.TypeDescriptor sınıfını inceleyeceğiz. Ardından çalışma zamanında bir sınıfın üst veri bilgilerini nasıl değiştirebileceğimizi göreceğiz.

Makale

    Çalışma zamanında(runtime) kodumuzla ilgili üst veri (metadata) bilgilerine erişme ihtiyacı duyabiliriz. .NET bizlere bu erişim için 2 farklı yol sunmaktadır: Bunlardan birincisi birçoğumuzun bildiği ve kullandığı en genel yöntem olan Reflection API’dir. Reflection ile managed kod içerisindeki tüm üst veri bilgilerine erişebilmemiz mümkündür. Reflection API’nin çalışma yöntemi genel olarak Type sınıfı üzerine kurulmuştur. Bir sınıfın içerdiği metotlar ve property’ler,metotların aldığı parametreler ve dönüş tipleri, attribute kullanımları vb. bilgilere erişebilmemizi kolayca sağlamaktadır. Ayrıca Assembly dosyaları ile ilgili verilere de erişebilmemiz mümkündür. Bu üst veri bilgileri, kodumuz derlendikten sonra oluşur ve çalışma zamanında  Reflection ile erişilebilir. Ancak, Reflection’ın bu bilgiler üzerinde değişiklik yapabilme gibi bir yeteneği yoktur.

    “Çalışma zamanında bu üst veri bilgileriyle ilgili değişiklikler yapabilir miyiz?” sorusunun cevabını System.ComponentModel.TypeDescriptor sınıfı vermektedir. TypeDescriptor sınıfı, üst veri bilgilerine erişebilmemiz için .NET’in sunmuş olduğu bir diğer yöntemdir ve Reflection üzerine kurulmuştur. Ayrıca çalışma zamanında üst veri bilgilerinde değişiklik (ekleme, silme vb.) yapılabilmesine de izin vermektedir. Tabi ki  bu değişiklikler metot bilgilerini kapsamamaktadır. MSDN, TypeDescriptor yapısını, IComponent arayüzünü gerçekleştiren(implement) sınıflar için genişletilebilir bir üst veri erişim yöntemi olarak tanımlamaktadır.

    .NET Framework, TypeDescriptor yapısının sunmuş olduğu esnek yapıyı bileşen(Component) nesnelerinin tasarım zamanında da(design-time) kullanmaktadır. Böylelikle gerekli yerlerde bu nesnelerin üst verilerinde değişiklikler yapabilmektedir. Aklınıza “TypeDescriptor tasarım zamanında nasıl ve ne zaman kullanılıyor?” gibi sorular gelebilir. Küçük bir Windows Forms uygulaması ile bunu açıklayalım:

    Bir form oluşturup üzerine bir TextBox sürükleyelim. TextBox için properties ekranında listelenmiş olan özelikleri inceleyelim:

textbox

    Şimdi de form üzerine bir ToolTip sürükleyelim. Ardından TextBox için properties penceresini yeniden kontrol edelim:

textbox-tooltip

    Yukarıdaki ekran görüntüsünde kırmızı renkle işaretlenmiş alana dikkat edecek olursanız TextBox için yeni bir property (Tooltip on tooltip1) oluştuğunu göreceksiniz. ToolTip form üzerine sürüklendikten sonra TextBox kontrolüne yeni bir Property eklenmiş gibi duruyor. Kodunuzdan kontrol ederseniz veya Reflector yardımı ile TextBox kodunu incelerseniz böyle bir Property tanımlamasının olmadığını farkedeceksiniz . Bu örnek, bizlere .NET Framework’ün tasarım zamanında üst veri bilgilerine erişmek için Reflection API dışında bir yöntem kullandığını göstermektedir. Bu yöntem TypeDescriptor sınıfıdır.

    .NET Framework, TypeDescriptor sınıfını IComponent arayüzünden türeyen bileşen sınıfları için kullanıyor olsa da bunu diğer tüm sınıflar ve nesneler için de kullanabilmemiz mümkündür. Ancak üst veri bilgilerinde yapılacak bu değişiklikler Reflection API tarafından görülemeyecektir.Şimdi TypeDescriptor’ı nasıl kullanabileceğimizi ve üst veri bilgilerindeki değişiklikleri nasıl gerçekleştirebileceğimizi görelim:

    Öncelikle Urun isimli basit bir sınıf oluşturalım.

public class Urun
{
    public string Barkod { get; set; }
    public string UrunAdi { get; set; }
    public double Fiyat { get; set; }
}

    Şimdi de TypeDescriptor ile Urun sınıfının Property bilgilerine hiçbir değişiklik yapmadan erişelim.

PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Urun));
foreach (PropertyDescriptor prop in properties)
{
    Console.WriteLine(prop.Name);
}
Console.ReadLine();

    TypeDecriptor.GetProperties metodu ile ilgili tipin Property bilgilerine erişiyoruz. Bu bilgiler bize System.ComponentModel.PropertyDescriptor tipindeki öğelerden oluşan bir System.ComponentModel.PropertyDescriptorCollection ile geliyor. Burada Urun sınıfına ilişkin bir düzenleme yapmadığımız için gelen bilgiler Reflection ile gelen bilgilerle aynı olacaktır. Kodumuzu çalıştırdığımızda  tüm Property bilgilerinin listelendiğini görüyoruz. 

urun_properties

    Varsayalım ki, Barkod’un üst veri bilgileriyle erişelememesini istiyoruz. Bu durumda ne yapabiliriz?

    Yapmamız gereken Urun sınıfı için özel bir TypeDescriptor oluşturmaktır. Bunu da System.ComponentModel.CustomTypeDescriptor sınıfını kullanarak gerçekleştirebiliriz.

public class UrunTypeDescriptor : CustomTypeDescriptor
{
    public UrunTypeDescriptor(ICustomTypeDescriptor parent)
        : base(parent)
    {
    }
    public override PropertyDescriptorCollection GetProperties()
    {
        //Sınıf içerisideki tüm Property’ler getiriliyor
        PropertyDescriptorCollection properties = base.GetProperties();
        List<PropertyDescriptor> modifiedProperties = new List<PropertyDescriptor>();
        //Adı ‘Barkod’ olmayan tüm Property’ler yeni listeye ekleniyor.
        foreach (PropertyDescriptor prop in properties)
        {
            if (prop.Name != "Barkod")
            {
                modifiedProperties.Add(prop);
            }
        }
        return new PropertyDescriptorCollection(modifiedProperties.ToArray(), true);
    }
}

    Kod bölümünde de gördüğünüz gibi oluşturduğumuz UrunTypeDescriptor sınıfında üst sınıf olan CustomTypeDescriptor sınıfındaki GetProperties metodunu override ediyoruz. Ardından adı Barkod olan Property tanımını almayarak oluşturduğumuz yeni listemizi dönüyoruz.

    Oluşturmuş olduğumuz UrunTypeDescriptor sınıfını kullanabilmek için özel bir TypeDescriptionProvider sınıfına da ihtiyacımız olacaktır. TypeDescriptor, türler ve nesnelerle ilgili üst veri bilgilerine System.ComponentModel.TypeDescriptionProvider sınıfları aracılığıyla erişmektedir. Herbir tip veya nesne için ayrı bir TypeDescriptionProvider eklememiz mümkündür. TypeDescriptor üst veri bilgilerine erişirken ilgili nesne veya tip için kayıtlı bir TypeDescriptionProvider olup olmadığını kontrol edecektir. Eğer var ise bu bilgi kullanılacaktır. Eğer tanımlanmamış ise varsayılan olarak Reflection API ile ulaştığımız üst veriler döndürülecektir.

    Bu bilgiden sonra Urun adlı sınıfımız için bir TypeDescriptionProvider oluşturalım.

public class UrunTypeDescriptionProvider : TypeDescriptionProvider
{
    public UrunTypeDescriptionProvider(TypeDescriptionProvider parent)
        : base(parent)
    {
    }
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        ICustomTypeDescriptor desc = base.GetTypeDescriptor(objectType, instance);
        return new UrunTypeDescriptor(desc);
    }
}

    Kod bölümünde de görmüş olduğunuz gibi GetTypeDescriptor metodunu override ettik ve TypeDescriptor olarak UrunTypeDescriptor sınıfının bir örneğini döndük. Şimdi yaptığımız bu eklemelerle Urun sınıfındaki Property bilgilerini getirelim.

static void Main(string[] args)
{
    TypeDescriptor.AddProvider(new UrunTypeDescriptionProvider(TypeDescriptor.GetProvider(typeof(Urun))), typeof(Urun));
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Urun));
    foreach (PropertyDescriptor prop in properties)
    {
        Console.WriteLine(prop.Name);
    }
    Console.ReadLine();
}

    Görüldüğü gibi, Urun tipi için yeni bir TypeDescriptionProvider ekledik. Ardından TypeDescriptor ile Property bilgilerine eriştik. Ekran görüntüsünde Barkod isimli Property’nin olmadığını göreceksiniz.

urun_custom_properties

    Eklemiş olduğumuz bu bilgi Urun tipi ve Urun tipinin tüm nesneleri için geçerli olacaktır çünkü düzenleme tip düzeyinde yapılmıştır. Yukarıda da bahsetmiş olduğumuz gibi bu düzenlemeyi nesne düzeyinde de yapabiliriz. Böylelikle, değişiklikler sadece ilgili nesne için geçerli olacaktır. Şimdi kodumuzu nesne düzeyinde düzenleme yapacak şekilde değiştirelim.

static void Main(string[] args)
{
    Urun u1 = new Urun();
    Urun u2 = new Urun();
    TypeDescriptor.AddProvider(new UrunTypeDescriptionProvider(TypeDescriptor.GetProvider(typeof(Urun))), u1);
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(u2);
    foreach (PropertyDescriptor prop in properties)
    {
        Console.WriteLine(prop.Name);
    }
    Console.ReadLine();
}

    Örnekte görüldüğü gibi u1 ve u2 isimli 2 Urun nesnesi oluşturduk. Ardından UrunTypeDescriptionProvider tanımlamasını u1 için kaydettik. Ancak u2 için Property bilgilerini sorguladığımızda bu değişikliğin etki etmediğini göreceğiz.

SONUÇ

    Bu makalede TypeDescriptor yardımı ile üst veri bilgilerine nasıl erişebileceğimizi ve bu bilgilerde nasıl değişiklik yapabileceğimizi genel olarak incelemeye çalıştık. Bir sonraki makalede PropertyGrid kontrolünü inceleyecek ve TypeDescriptor kullanarak PropertyGrid üzerinde gösterilen bilgileri nasıl değiştirebildiğimizi göreceğiz.

Cemil ABİŞ
http://www.cemilabis.com
http://twitter.com/cemilabis