Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

LINQ Sorgusu mu? ForEach mi? Bir Türlü Karar Veremedim

Cuma, 28 Mayıs 2010 11:40 by bsenyurt

Merhaba Arkadaşlar,

Bilim Kurgu fanatiklerinin kafasında her zaman hayranı oldukları filmlerden kesitler, sahneler kalır. Matrix filmini izleyenler eminimki Neo' ya uzatılan kırmızı ve mavi hap serenatını gayet iyi hatırlayacaktır. Morpheus haplardan birisinde Alice Harikalar Diyarının kapılarını ardına kadar açabileceğini ifade ederken, diğer hapı yuttuğunda, Neo' nun yatağında hiç bir şey olmamış gibi uyanacağını ve tüm bunların bir hayalden ibaret olduğunu düşüneceğini belirtir. Tabi Neo amacına ulaşmak için zaten hangi hapı içmesi gerektiğini biliyordur ki son bölümde aslında gerçekten hapı yutmaktadır Yell

Bizde yazılımcılar olarak bazen karar verirken tabir yerinde ise sürüncemede kalabiliriz. Böyle durumlarda ufak tefek gözüken noktaların aslında çok büyük riskler taşıdığını da düşünmemiz gerekmektedir. Çünkü karar vermek için basit bir kaç test kodu çok işimize yarayacaktır. İşte bu yazımızda böyle bir konuya değiniyor olacağız.

Aslında konunun çıkış noktası Microsoft Teknoloji Günleri Akşam Sınıfındaki bir meslektaşımın sorusu oldu. Değerli meslektaşım uygulama kodunda koleksiyon bazlı sorgulamaları gerçekleştirirken pek çok vakada foreach döngülerini tercih ettiğini söyledi. Tabi her durumda değil. Bende bu noktada aynı amaca hizmet eden bir LINQ sorgusu ile ForEach çalışması arasındaki performans farklılıklarını irdelemeye karar verdim. Nitekim performans her zaman için karar vermeden önem arz eden kriterlerden birisidir. Anlayacağınız basit bir test ve sonuçlarını irdeliyor olacağız bu kısa yazımızda.

Örnek uygulamamızda Enumerable.Range metodu yardımıyla elde edilen bir int sayı dizisi içerisinde 2 ile tam bölünebilen sayıların adedini hesap ettirmekteyiz. Tahmin edeceğiniz üzere bu tip bir işlemi LINQ sorgusu yardımıyla anlamlı bir kod ifadesi ile yerine getirebiliriz. Ayrıca bunu bir foreach döngüsü ile de gerçekleştirebiliriz. İşte test kodlarımız.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace LINQForEachPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 1; i < 10; i++)
            {
                IEnumerable<int> range = Enumerable.Range(i,(i+1)*10000000);
                WithLinq(range);
                WithForeach(range);
            }
           
        }

        static void WithLinq(IEnumerable<int> range)
        {
            Stopwatch sWatch = new Stopwatch();
            sWatch.Start();

            int count = (from i in range
                         where i % 2 == 0
                         select i).Count<int>();
            Console.WriteLine(count.ToString());

            sWatch.Stop();
            Console.WriteLine("LINQ Total Time : {0}",sWatch.ElapsedMilliseconds.ToString());
        }

        static void WithForeach(IEnumerable<int> range)
        {
            Stopwatch sWatch = new Stopwatch();
            sWatch.Start();

            int count = 0;
            foreach (int i in range)
            {
                if (i % 2 == 0)
                    count++;
            }
            Console.WriteLine("{0}",count.ToString());

            sWatch.Stop();
            Console.WriteLine("ForEach Total Time : {0}", sWatch.ElapsedMilliseconds.ToString());
        }
    }
}

Örnekte arka arkaya 10 deneme yapılmaktadır. WithLinq metodu LINQ sorgusunu kullanarak ikiye tam bölünen sayıların adedini vermektedir. WithForeach metodu ise aynı işlemi foreach döngüsü yardımıyla gerçekleştirmektedir. Stopwatch tipi yardımıyla her hesaplamanın toplam süresi bulunmaktadır. Uygulamanın Intel çift çekirdek işlemcili, 4Gb Ram' i olan makinemdeki çalışma zamanı sonuçlarından bir tanesi aşağıdaki ekran görütüsündeki gibidir.

Aslında her zaman için süreler farklı olacaktır ancak grafiksel eğriler benzer olacaktır. Durumun daha net bir şekilde görülmesi için değerlerin Excel üzerinde Chart olarak gösterilmesi yeterlidir. İşte sonuçlar.

Görüldüğü üzere foreach döngüsü ile yapılan hesaplamalar değer aralığı büyüse dahi LINQ sorgusuna göre daha kısa sürede icra edilmektedir. Buna göre foreach' in daha hızlı olduğunu söyleyebilir miyiz? Bu senaryo için evet. Wink Ama bildiğiniz üzere LINQ daha karmaşık sorgular yazılması noktasında elbetteki iç içe geçecek sayısız foreach kullanımından çok daha etkili bir yöntemdir. Ancak başta da belirttiğimiz gibi insan bir an için hangi hapı yutacağına karar veremiyor. Tabi farklı sorgulama senaryoları ile farklı denemeler yaparak karşılaştırmalara devam etmekte yarar olabilir. Bu kutsal görevi de siz değerli okurlarıma bırakıyorum. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

LINQForEachPerformance.rar (23,30 kb) [Örnek Visual Studio 2010 Ultimate ile geliştirilmiş ve test edilmiştir]

Tags:   , , ,
Categories:   C# | LINQ
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (9) | Comment RSSRSS comment feed
Bookmark and Share

Değişken Atamalarında Bir Efsane

Pazartesi, 12 Nisan 2010 11:30 by bsenyurt

Merhaba Arkadaşlar,

Yandaki resimde görülen kahramanları tanıyanınız var mı? Biraz düşünün isterseniz...Jamie Hyneman ve Adam Savage ikilisi tarafından sunulan ve Wikipedia' daki verilere göre Discovery Channel aracılığıyla ilk yayınını 23 Ocak 2003 tarihinde gerçekleştiren MythBusters isimli bu belgesel dizide, bilimsel metodlardan yararlanılarak bazı şehir efsanelerinin gerçeklikleri ispatlanmaya çalışılmaktadır.

Aslında film sanayisinin ne kadar çok geliştiğini ispat eden bir dizidir. Nitekim dizinin sunucuları Hollywood' un tanınan film efekti teknisyenleridir (ki bu nedenle bilimsel anlamda gayet donanımlıdırlar) ve son derece ilginç efsaneleri araştırırlar. Örneğin "Yağmurda koşan mı yoksa yürüyen mi daha çok ıslanır?", "Benzin istasyonunda cep telefonu kullanmak patlmaya neden olur mu?", "Piercing yapan insanları yıldırım çarpar mı?", "Emprise State binasının tepesinden yere serbest düşüşle bırakılan metal bir para betona saplanır mı?" ve daha nice ilginç efsaneyi bilimsel taktikler ile çözmüşlerdir ve çözmeye devam etmektedirler. Açıkçası bende evdeki afacan müsade ettiği sürece bu tip belgeselleri kaçırmamaya çalışıyorum ve size de izlemenizi şiddetle tavsiye ediyorum.

Gelelim bu dizinin bu yazımızdaki konuyla ilgisinin ne olduğuna. Her zamanki gibi önce güzel bir giriş yapalım istedim. Ama asıl mesele C# tarafında da bazı efsanelerin olabileceği. Üstelik bunların çoğundan habersisiz. Ancak Internet üzerinde siz de benim gibi yeteri kadar araştırma yaparsanız bu konulara ilişkin son derece güzel yazıların olduğunu keşfedebilirsiniz. Ben bu yazımızda değişken atamaları ile ilişkili bir konuyu incelemeye çalışacağım. Olayın çıkış noktası ise aşağıdaki kod parçamız olacak.

Vaka 1;

using System;

namespace AssignMyth
{
    class Program
    {
        static void Main(string[] args)
        {
            double x, y, z;

            x = y = z = Math.PI;

            Console.WriteLine("x={0}\ny={1}\nz={2}",x,y,z);
        }
    }
}

Uygulamanın çalışma zamanı çıktısı aşağıdaki gibidir.

Bu kod parçasında double tipinden olan x, y ve z değişkenlerine tek satırda Pi değerinin atanması söz konusudur. Bu son derece doğaldır nitekim eşitliğin sağından başlayan bir atama sırası mevcuttur. Hatta buna göre aşağıdaki ifade de doğrudur.

x = (y = (z = Math.PI));

Nitekim parantezlerin olaya kattığı her hangibir öncelik bulunmamaktadır. Fakat aşağıdaki kod parçasını göz önüne aldığımızda eşitliğin en sağ tarafındaki değerden başlayarak en soldaki değişkene doğru yapılan atamaların her zaman sanıldığı gibi olmadığı izlenimine varmamız söz konusudur.

Vaka 2;

using System;

namespace AssignMyth
{
    class Program
    {
        static void Main(string[] args)
        {
            object x;
            double y;
            const float z = 3.14f;

            x = y = z;

            Console.WriteLine(x.GetType().ToString());
        }
    }
}

Ekran çıktısı aşağıdaki gibi olacaktır.

Hımmm...Enteresan bir durum söz konusu sanırım. Surprised Eşitliğin en sağında yer alan z isimli değişken aslında float tipinden tanımlanmıştır. Ardından hemen solunda yer alan double tipinden değişkene aktarılmıştır. y isimli değişken double tipindendir. Son olarak eşitliğin en solunda yer alan x isimli object tipinden değişkene bir atama yapılarak 3.14 değeri en sağdan en soldaki değişkene doğru taşınmıştır. Lakin değişkenin tipi eşitliğin en sağından en soluna kadar korunamamıştır. Wink Ekran çıktısına dikkat edilecek olursa, x değişkeni gelen değeri float tipi yerine double tipi olarak ele almıştır. Yani x=y=z atamasında en soldaki x değişkeninin tipi y' nin tipine göre belirlenmektedir. Bu durumda eşitliğin en sağındaki değişkenin tipinin en soldaki object tipine taşınmasında bir anlamda bozulma olduğunu düşünebiliriz. Konuyu biraz daha ileri götürelim ve bu kez aşağıdaki kod parçasını göz önüne alalım.

Vaka 3;

using System;

namespace AssignMyth
{
    class Program
    {
        static void Main(string[] args)
        {
            Person burak = new Person();

            object name = burak.Name = null;
            Console.WriteLine("name null mı? {0}",name==null);
            Console.WriteLine("burak.Name null mı? {0}",burak.Name==null);
        }
    }

    public class Person
    {
        private string _name;

        public string Name
        {
            get { return _name==null?"":_name; }
            set { _name = value; }
        }
    }
}

Dilerseniz kodun çalışma sonrası üretilen çıktıyı görmeden önce neler olduğuna bir bakalım. Person sınıfı içerisinde Name isimli bir özellik(Property) yer almaktadır. Bu özelliğe ait get bloğunda dikkat edilecek olursa null kontrolü yapılmaktadır. Bu kontrole göre String tipinden olan _name alanının değerinin null olması halinde geriye boş bir string döndürülmesi tercih edilmiştir. Aksi durumda ise _name değerinin kendisi döndürülmektedir. Buna göre aslında Person tipinin Name özellliği ya boş string ya da bir içeriğe sahiptir.

Main metodu içerisindeki kod parçasına baktığımızda ise ilgi çekici nokta null değer atamasının yapıldığı satırıdr. Eşitliğin en sağında yer alan burak isimli değişkenin Name özelliğine null değer atanmaktadır. Sonrasında ise bu değer object tipinden olan name değişkenine taşınmaktadır. İzleyen iki satırda ise object tipinden olan name değişkeni ile burak nesne örneğinin Name özelliklerinin değerlerinin null olup olmadığı kontrol edilmekte ve sonuçlar ekrana yazdırılmaktadır.

Normal şartlarda düşündüğümüzde burak.Name için geriye null değer dönmesi söz konusu değildir. Nitekim get bloğunda bunun için bir kontrol yapılmaktadır. Bu durumda son satırın sonucunda false değer dönmesi beklenmektedir. Diğer yandan en soldaki name değişkenine yapılan atamaya göre de null değer yerine "" değeri taşınmış olmalıdır ki buna göre de name değerinin null olması sonucunun false dönmesi gerekmektedir. Ama uygulamayı çalıştırdığımızda sonuçların aşağıdaki gibi olduğu görülecektir.

Oda ne? Surprised burak.Name== null için False değer dönmüştür ve bu beklediğimiz sonuçtur. Ancak name==null kontrolünün değeri true olarak gelmiştir. Oysaki atamaya göre name değişkenine "" değerinin gelmesi ve bu nedenle null olmaması gerekmektedir. İlginç değil mi?

Sonuç olarak bu yazıda bahsettiğimiz şekliyle gerçekleştirilen atamalarda, eşitliğin en sağındaki değerin sola doğru taşındığı efsanesinin tam olarak doğru olmadığı ispatlanmış bulunmaktadır. Nitekim ilk vakada eşitliğin en sağından soluna aynı değer başarılı bir şekilde atanmaktadır. Ancak ikinci vakaya göre aslında en soldaki değişkenin bir sağındakinin tipine büründüğü de görülmektedir. Üstelik Vaka 3' e göre en soldaki değişken en sağdan atanan değere bürünmüş ve bir sağındakini kaale bile almamıştır...Kafanız karıştı mı? Bakalım başka ne gibi efsaneler var. İlerleyen yazılarda değinmeye çalışıyor olacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

AssignMyth_RC.rar (20,03 kb)

Tags:  
Categories:   C#
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

Object vs Dynamic

Perşembe, 1 Nisan 2010 00:10 by bsenyurt

Merhaba Arkadaşlar,

Ayrıntılar detaylarda saklıdır. Bu cümleyi çok severim. Sevdiğim özlü sözler arasındadır. Gerçel bir nesnenin ne kadar kaliteli olduğunu anlamak için detaylarına bakmak gerekir. İşçiliğine, kullanılan malzemeye, malzemelerin uyumuna vs...Hatta benzer diğerleri ile olan kalite farkını anlamak için bile. Çok doğal olarak yazılım dünyasında da bir takım konuların anlaşılabilmesi, kavranabilmesi, benzerleri ile olan farklarının irdelenebilmesi için mutlaka detaylara bakmak, ama sıkılmadan bakmak gerekir. Aynen bu günkü yazımızda yapacağımız gibi.

Bu yazımızda Dynamic Language Runtime kullanımında büyük öneme sahip olan dynamic ile .Net Framework' ün ilk çıktığı zamandan beri var olan Object tipi arasındaki farklılıkları görmeye çalışacağız. Bunun için kod tarafında biraz daha detaya girmemiz gerekecek. Çok derin değil belki de ama aradaki farklılıkları çıkartabilmek adına önemli detaylar. Başlamadan önce örneklerimizi Visual Studio 2010 Ultimate RC sürümünde geliştirdiğimizi ve ilerleyen sürümlerde farklılıklar olabileceğini hatırlatmak isterim.

Öncelikli olarak işe aşağıdaki basit kod parçası ve Object tipini ele alarak başlayalım.

Case 1;

object pi = Math.PI;
Console.WriteLine(pi.GetType().ToString());

İlk satırda Math.PI sabit değerinin object tipine atandığını görüyoruz. Aslında bilinçsiz olarak bir tür dönüşümü söz konusu(Implicitly Type Casting). Burada herhangibir sıkıntı yok. pi.GetType satırının çalışma zamanı çıktısı ise System.Double olmalıdır. Nitekim eşitliğin sağ tarafından gelen Math.PI, double tipinden olduğu için pi isimli object tipi üzerinden ele alınsa da kendi tipini taşımış olmaktadır.

Şimdi kodumuzu aşağıdaki gibi değiştirdiğimizi düşünelim.

Case 2;

object pi = Math.PI;
object square = pi * 10 * 10;

Bu kez object tipinden olan pi ile iki sayısal değeri(ki bunlarda int tipindendir) matematiksel bir işleme tabi tutup sonucu yine bir object tipine atamaya çalışıyoruz. Ancak kodu derlediğimizde Compiler' ın bir derleme zamanı hata mesajı ürettiğini görürüz.

Hımmm...Aslında bu son derece doğal bir sonuç. Nitekim derleme zamanında pi değişkeninin tipi object ve biz object tipi ile int tiplerini işleme sokmaya çalışıyoruz. Burada çözüm, dönüştürme işlemini bilinçli olarak yapmaktan ibaret(Explicitly Type Casting). Yani kodu aşağıdaki gibi düzenlemekten;

Case 3;

object pi = Math.PI;
object square = (double)pi * 10 * 10;

Dikkat edileceği üzere pi değişkeni bilinçli olarak double tipine dönüştürülmüş durumdadır. Aslında az önceki senaryomuzda pi' nin taşıdığı tipin double olduğunu görmüştük. Yine de başka tipler ile işleme tabi tuttuğumuzda derleme zamanı hatası ile karşılaşmaktan kurtulamadık. Çünkü pi değişkeni double tipten değer taşıyan bir object idi aslında.

Şimdi durumu biraz daha entersan bir hale getireceğiz. Aşağıdaki kod parçasını göz önüne alın ve kodu kafanızda derleyip hata olup olmadığını söyleyin. Sonrasında ise çalışma zamanında bir hata oluşup oluşmayacağını düşünün ve bir karar daha verin. En sonunda ise bunu, konu ile ilgili yakın arkadaşlarınızla tartışın Wink

object pi = Math.PI;
object square = (int)pi * 10 * 10;

Derleme zamanında herhangibir hata mesajı alınmayacaktır. Ancak çalışma zamanına(Runtime) geçtiğimizde aşağıdaki ekran görüntüsünde yer alan istisna(Exception) ile karşılaşırız.

Upsss!!!

Sorun şudur; pi değişkeni object tipinden tanımlanmıştır ve eşitliğin sağ tarafına göre double tipinden bir değer taşımaktadır. Ancak bu tip cast operatörüne göre int tipine dönüştürülmeye çalışılmaktadır. Oysaki çalışma zamanının burada beklediği tam olarak double tipine dönüştürme işlemidir.

Dolayısıyla object tipini kullandığımız bu senaryoda matematiksel işlemlerin yapılabilmesi için, mutlaka doğru tipe dönüşüm gerçekleştirilmelidir. Eğer bir dönüştürme işlemi yapmassak, derleme zamanında hata mesajı alırız. Ancak tip dönüşümü yaparkende koltuğumuzda rahat edemeyiz, çünkü yanlış tipe dönüştürme işlemleri çalışma zamanı istisnaları ile cezalandırılır. Buna göre gerçekten beklenen tipe dönüşüm işlemi sağlanmalıdır.

Gelelim yeni gözdemiz olan Dynamic tipe. Yukarıdaki senaryoları bire bir, ama bu kez dynamic tipini kullanarak değerlendireceğiz.

dynamic pi = Math.PI;
Console.WriteLine(pi.GetType().ToString());

Bu kez eşitliğin sol tarafında dynamic tipi vardır. object tipinin kullanıldığı örnektekine(Case 1) benzer olaraktan pi değişkeni yine double tipinden bir değer taşımaktadır. Çalışma zamanının üreteceği sonuçta aynı olacaktır. Ancak aşağıdaki kod parçasını göz önüne aldığımızda,

dynamic pi = Math.PI;
dynamic square = pi * 10 * 10;

object tipinin kullandığımız örneğe(Case 2) baktığımızda derleme zamanında bir hata aldığımızı hatırlıyoruzdur sanırım. Oysaki dynamic tipi derleme zamanı ile ilgilenmemektedir. Çalışma zamanında ise pi' yi gereken tipte ele almaktadır. Zaten object tipini kullandığımız üçüncü senaryomuzu ele almamıza da gerek kalmamıştır. Wink

Buna göre şöyle bir sonuca varabiliriz. Dynamic tipin kullanıldığı hallerde derleyicinin(Compiler) derleme zamanında tip tahmini yapmasına gerek yoktur. Bu çözümleme işi çalışma zamanında yapılmaktadır.

Tabi bu sonuçlara göre "her yerde dynamic tip kullanalım mı?" sorusu da gündeme gelir. Ancak "metodlara parametre aktarımlarında dynamic kullanımının kodun kırılmasına neden olması söz konusu olabilir mi?" sorusu daha da önemlidir. Şimdi bu durumu ele almaya çalışalım. İşte kodlarımız;

            dynamic R = "on iki";
            Calculate(R);

            #endregion

            #endregion

        }

        static double Calculate(double r)
        {
            return Math.PI * r * r;
        }

Bu kod parçasında kodu kırmaya yönelik olarak bir hamle yapıldığı düşünülebilir. Calculate metodu double tipinden bir değer beklemektedir. Biz ise dynamic olarak tanımladığımız R değişkenine string bir değer atayarak parametre gönderme işlemini gerçekleştirmekteyiz. Dynamic kullanımına göre derleme zamanında bir hata mesajı alınmaması normaldir. Benzer şekilde çalışma zamanında gelen string tipinin, double tipe otomatik olarak dönüştürüleceğini de düşünebiliriz. Eğer böyle olsaydı, kodun dynamic tipi yardımıyla kolayca kırılabileceği sonuçlarına varabilirdik. Şükür ki çalışma zamanında aşağıdaki hata mesajı ile cezalandırılırız. Laughing

Ancak,

dynamic R=12;

atamasını yaparsak herhangibir sorun ile karşılaşmayız. Çünkü Calculate metodunun beklediği(veya taşıyabildiği) tipte bir değişkenin gönderilmesi sağlanmaktadır.

Sonuç olarak bir metoda dynamic tipte veri atayabiliyor olsakta bu, metoda her tipten değeri aktarabileceğimiz anlamına gelmemektedir. Gerçekten metodun beklediği veya kabul edebileceği türden bir değişkenin atanması şarttır.

Son senaryomuzu object tipi ile düşündüğümüzdeyse yine başta açıklanan 3 vakanın gerçekleşeceği görülecektir. Gelin bu durumları bir kere daha inceleyelim. Kodu ilk etapta aşağıdaki gibi düzenleyelim.

object R = 12.1;
Calculate(R);

Bu durumda daha derleme zamanında hata mesajı alırız. Aşağıdaki şekilde olduğu gibi.

Dolayısıyla tip dönüşümü yapmamız şarttır. Öyleyse yapalım. Laughing

object R = 12.1;
Calculate((int)R);

Derleme zamanında hata yok. Süper...Ama oda ne? Çalışma zamanında yine hata aldık. Undecided

Tahmin edileceği üzere object tipi kullanıldığından çalışma zamanında tam olarak double tipinden bir değer taşınması beklenmektedir. 12.1 double olarak ifade edilmesine rağmen int tipine yapılan dönüşüm geçersizdir(Dynamic tipin kullanımının tam aksine). Dolayısıyla kodun aşağıdaki gibi düzenlenmesi gerekir.

object R = 12.1;
Calculate((double)R);

Bu durumda çalışma zamanında her hangibir hata mesajı alınmadan ilerlenebilecektir.

Görüldüğü üzere derinlerde, dynamic ve object kullanımları arasında belirgin farklılıklar bulunmaktadır. Bunların sebepleri aşikardır. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek, hepinize mutlu günler dilerim.

DynamicVsObject_RC.rar (20,73 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

.Net 4.0 Öncesi ThreadPool Kullanımı

Çarşamba, 23 Aralık 2009 12:15 by bsenyurt

Merhaba Arkadaşlar,

İlk okulda eminimki pek çok arkadaşımız havuz problemlerinden müzdarip olmuştur. Genellikle bu havuzlarda ikiden fazla musluk olması neredeyse garantidir ve genellikle bu musluklardan bazıları havuzu belirli sürelerde doldururken, bazılarıda belirli sürelerde boşaltır. Hatta zamanla bu muslukların ne kadar hızla su doldurduğu veya boşalttığıda işin içerisine girer ve aslında sadece yüzmek için kullanabileceğimiz güzelim havuz koca bir problem haline dönüşür. Ben açıkçası bu problemleri çözmekte hep yetersiz kalmışımdır. Hatta çoğunlukla atmasyon cevaplar ürettiğimi itiraf edebilirim. Nitekim havuz deyince aklıma genellikle yandaki resimde görülen manzara gelir. Peki havuz problemlerinden kurtulabildik mi? Ihhh Laughing Çünkü artık Multi-thread uygulamalar ile uğraşmaktayız ve elimizde yönetebileceğimiz n sayıda Thread olabiliyor. Bu Thread' ler bir havuz içerisinde toplanabilir mi peki? Evet toplanır...İşte bu günkü konumuz. ThreadPool kullanımı.

ThreadPool; arka planda belli bir işi yapmak üzere planlanmış görevlerin Thread' lere bölünmesi ve bu Thread' lerin bir koleksiyon şeklinde tutularak asenkron işleyişlerinin yönetilmesi amacıyla kullanılan sarmalayıcı(Wrapper) bir tip olarak düşünülebilir. Genellikle sunucu tabanlı uygulamalarda değerlendirildiği gözlemlenmektedir. Örneğin Windows Service' leri içerisinde ThreadPool kullanımı mantıklıdır. Bunun dışında dosya giriş çıkış(IO), yapay zeka, veritabanı, Karmaşık Matematik problemlerin çözüm algoritmaları gibi Multi-Threading gerektiren işlemlerde değerlendirilebilir.

Burada önemli olan noktalardan birisi çalışma modelidir. Aslında yapılmak istenen işe ait havuza gelen her talep, havuz içerisinde bir Thread' e atanır ve asenkron olarak yürütülür. Burada ana uygulama Thread' ine bir bağımlılık söz konusu değildir(ki ana uygulamanın ThreadPool tarafından değerlendirilen işlemlerin tamamlanmasını beklemesi yönünde uyarılması gerekebilir). Hatta alt taleplerin bekletilmeside söz konusu değildir. Bununla birlikte önemli olan noktalardan biriside, işi biten bir görevin sahibi olan Thread' in tekrardan kullanılıncaya dek havuzda yer alan kuyruğa atılmasıdır. Burada kuyrukta yer alan Thread' in, ana uygulama tarafından tekrardan kullanılması halinde, Thread oluşturma maliyetlerinin önüne geçilmesi mümkün hale gelmektedir ki bu bir avantajdır. Tabiki havuzunda belirli bir kapasitesi vardır(Varsayılan olarak 25 Thread). Bu kapasitenin dolu olması halinde ek olarak gelen görevler kuyrukta kalır ve ancak işleyen Thread' ler çalıştırılmaya müsait olduklarında icra edilebilir.

Buraya kadar anlattıklarımızı değerlendirecek olursak, Thread yönetiminin daha kolay bir şekilde ele alınabildiğini görebiliriz. Hatta ThreadPool' un temel olarak iki fonksiyonu olduğunu da düşünebiliriz. Bunlardan birincisi havuzda yer alan Thread' lerin üstlendiği işlerin tamamlanma durumlarını takip etmek, koordinasyonu sağlamak ve ikinci olarakta Thread koleksiyonunu bir kuyruk düzeninde yönetmektir. Tabi bu durumda ThreadPool tipinin yönettiği koleksiyona thread ekleme(enqueue) ve çıkarma(dequeue) işlemleri ile ilişkili bir algoritma içeridiğini ifade edebiliriz. Hatta kaç Thread' e ihtiyaç duyulacağını hesaplamak gibi önemli yeteneklere sahip olduğunu da söylemeliyiz. Açıkçası bu iki fonksiyonelliğin içerdiği algoritmaları geliştirmekle uğraşmak yerine işi ThreadPool' a bırakmak daha optimal bir çözüm olarak görülmelidir. Yine de istendiğinde kendi ThreadPool tiplerimizi de geliştirebiliriz. Tabi bilindiği üzere .Net Framework 4.0 ile birlikte ThreadPool üzerinde de bazı geliştirmeler yapılmıştır. Bu geliştirmelere göre, havuzun çalışma mantığı değişmiştir. Ancak şu anda bu konuya girmeyeceğiz.  Önce var olan modeli bir öğrenelim. (.Net 4.0 tarafındaki ThreadPool kabiliyetlerini biraz daha cesaret toplayıp ileride incelemeyi ve sizlere aktarmayı planlıyorum Wink ) Dilerseniz bu kadar laf kalabalığından sonra basit bir örnek ile devam edelim. Visual Studio 2008 ortamında ve .Net Framework 3.5 tabanlı olaraktan bir Console uygulaması geliştireceğiz. İşte örnek kodlarımız.

using System;
using System.Diagnostics;
using System.Threading;

namespace WhatIsThreadPool
{
    class Program
    {
        static ManualResetEvent[] mrEvents;
        static int[] testNumbers; // Faktöryel değerleri hesap edilecek sayıların tutulacağı dizi.
        static long[] results; // Faktöryel sonuçlarının tutulacağı dizi
        static int testCount = 5; // Denemesayısı

        static void Main(string[] args)
        {
            Console.WriteLine("Başlamak için bir tuşa basınız. Ana Thread Id : {0}",Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadLine();

            // Ana Thread' i pool içinde çalışan Thread' lerin bittiği konusunda bilgilendirecek ManualResetEvent nesne dizisi oluşturulur
            mrEvents = new ManualResetEvent[testCount];
            results = new long[testCount];
            testNumbers = new int[testCount];
            Random rnd = new Random();

            Stopwatch watcher = new Stopwatch();
            watcher.Start();

            for (int i = 0; i < testCount; i++)
            {
                // Başlangıçta ManualResetEvent nesnesi false değer ile üretilir.
                mrEvents[i] = new ManualResetEvent(false);
                testNumbers[i] = rnd.Next(1, 20); // örnek bir test sayısı üretimi
                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadWork), i);
            }

            // Tüm Thread' lerin işi bitinceye kadar ana uygulamayı duraksat
            WaitHandle.WaitAll(mrEvents);

            watcher.Stop();
            Console.WriteLine("\nİşlemler tamamlandı...Toplam Süre {0} \n",watcher.Elapsed.TotalMilliseconds.ToString());

            for (int i=0;i<results.Length;i++)
            {
                Console.WriteLine("\t\t{0} için sonuç {1} ",testNumbers[i].ToString(), results[i].ToString());
            }
            Console.ReadLine();
        }

        // ThreadPool içerisindeki Thread' lerin işaret ettiği metod. WaitCallback temsilcisinin bildirimine uygun olaraktan object tipinden parametre almakta ve değer döndürmemektedir
        static void ThreadWork(object obj)
        {
            int currentNumber = (int)obj;

            Console.WriteLine("{0} sayısı için hesaplama. Current Thread Id : {1}",testNumbers[currentNumber].ToString(),Thread.CurrentThread.ManagedThreadId.ToString());
           
            // Faktöryel hesaplamasını gerçekleştiren metod çağrısı
            results[currentNumber]=Factorial(testNumbers[currentNumber]);
            // Ana Thread' in bilgilendirilmesi sağlanır.
            mrEvents[currentNumber].Set();
        }

        // Faktöryel hesabını yapan recursive metod
        static long Factorial(int number)
        {
            long result;
            if (number == 0
                || number == 1)
                result = 1;
            else
                result = Factorial(number - 1) * number;

             return result;
        }
    }
}

Uygulamamızda 1 ile 20 arasındaki rastgele 5 sayının Faktöryel değerlerinin hesaplanmasında ThreadPool' dan yararlanılmıştır. Örneği çalıştırdığımızda her seferinde farklı sonuçlar elde etmemiz söz konusudur. İşte benim yakaladığım sonuçlardan birisi.

Dikkat edileceği üzere ThreadPool tarafında iki Thread üretilmiş ve 5 sayısal değer için gerçekleştirilen faktöryel hesaplamaları bu Thread' ler tarafından ele alınmıştır.

Kişisel Not : Tabi işin kolayına kaçtığımızı ifade etmek isterim. Özellikle 21 sayısı dahil sonraki faktöryel hesaplarında eksi değerlere geçtiğimizden sayı aralığımız çok sınırlı. Bir diğer yanıltıcı nokta ise toplam hesaplama süresi. Buradaki faktöryel işlemleri aslında pek yorucu işlemler değildir. Bu nedenle aynı örneği Thread mekanizması olmadan çalıştırdığınızda hesaplama süresinin çok çok daha kısa sürdüğünü görebilirsiniz. Elbette bizim odaklandığımız nokta bu değil. Aslında ThreadWork metodunun çalıştırdığı Factorial fonksiyonunun gerçekten uzun süren yoğun işlemler gerçekleştirdiği düşünülebilir. Bu durumda ThreadPool mekanizması bize zaman yönünden avantaj getirecektir. Hatta işlemlerin uzunluğuna göre Thread sayısını arttırmasıda mümkün olabilir. Buna göre çıkartmamız gerken bir derste gerçekten ihtiyaç olunduğunda ThreadPool modelinin kullanılmasının uygun olduğudur.

Tabi dikkat edilmesi gereken bir kaç nokta olduğunu söyleyebiliriz. Öncelikli olarak Workflow Foundation mimarisinde de sık sık karşımıza çıkan ManualResetEvent tipinden bahsedelim. Bu tipten yararlanarak bir Thread' den başka bir Thread' e işin bittiğine dair sinyal gönderilmesi mümkündür. Bu noktada ThreadPool içerisinde çalışmakta olan Thread' lerin işlemlerini bitirmesini takiben, ThreadPool' un sahibi olan ana Thread' in(ki burada Main metodunun yer aldığı Program tipine ait Thread' den bahsediyoruz) işlemlerin tamamlandığı yönünde uyarılması gerekmektedir. Bu sebepten ManualResetEvent nesne örnekleri, Set metodu yardımıyla diğer Thread' i işlemlerin bittiği yönünde uyarmaktadır.

Ana uygulama için önem arz eden noktalardan biriside, ThreadPool içerisinde çalıştırılmakta olan Thread' lerin tamamının görevleri sonuçlanıncaya kadar beklemesi gerekebileceğidir. Bu sebepten ManualResetEvent tipinden olan dizinin tüm elemanlarının Set metodunun çalıştırılıp ana uygulama Thread' ini uyarması gerekmektedir. Örnek kod parçasından da görüleceği üzere söz konusu duraksatma işlemi için WaitHandle tipine ait static WaitAll metodunun çağırılması yeterlidir.

Thread' ler ile ilişkili görevlerin ThreadPool tarafından yönetilen kuyruğa atılmaları için QueueUserWorkItem metodundan yararlanılmaktadır. Bu metodun ilk parametresi dikkat edileceği üzere WaitCallback temsilcisi(delegate) tipindendir. Bu temsilci object tipinden parametre alan ve geriye değer döndürmeyen(void) metodları işaret edebilir. Buna göre Thread' lerin eşleştiği görevleri üstlenen fonksiyonların söz konusu metod modeline uygun olması gerekmektedir. İlgili metod içerisinde dikkat edileceği üzere ManualResetEvent nesne örneği üzerinden Set metodu çağrısı gerçekleştirilmektedir. Tabi C# 3.0 tarafında gelen lambda operatörü(=>) sayesinde aynı kodun aşağıdaki şekilde yazılmasıda mümkündür.

for (int i = 0; i < testCount; i++)
            {
                // Başlangıçta ManualResetEvent nesnesi false değer ile üretilir.
                mrEvents[i] = new ManualResetEvent(false);
                testNumbers[i] = rnd.Next(1, 20); // örnek bir test sayısı üretimi
                // ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadWork), i);

                ThreadPool.QueueUserWorkItem((obj) =>
                {
                    int currentNumber = (int)obj;

                    Console.WriteLine("{0} sayısı için hesaplama. Current Thread Id : {1}", testNumbers[currentNumber].ToString(), Thread.CurrentThread.ManagedThreadId.ToString());

                    // Faktöryel hesaplamasını gerçekleştiren metod çağrısı
                    results[currentNumber] = Factorial(testNumbers[currentNumber]);
                    // Ana Thread' in bilgilendirilmesi sağlanır.
                    mrEvents[currentNumber].Set();
                }
                , i);               
            }

Böylece geldik bir yazımızın daha sonuna. Umarım ThreadPool konusunda biraz fikir sahibi olabilmişizdir. Tekraradan görüşünceye dek hepinize mutlu günler dilerim.

WhatIsThreadPool.rar (23,73 kb)

Tags:   ,
Categories:   C#
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (2) | Comment RSSRSS comment feed
Bookmark and Share

C# Temelleri - Olayları(Events) Kavramak

Çarşamba, 6 Haziran 2007 19:01 by bsenyurt

Olaylar (Events), görsel uygulamalar ile uğraşan her geliştirici tarafından bilinçli veya bilinçsiz bir şekilde kullanılmaktadır. Nesne yönelimli programlama ortamında olayları tanımlamak için klasik olarak verilen bir örnek vardır. Hepinizin bir sonraki cümlede ne diyeceğimi bildiğinizden eminim. Söz konusu örnekte görsel ortamda yer alan bir düğme kontrolü (çoğunlukla Button sınıfına ait bir nesne örneği) ve bu düğmeye kullanıcının mouse ile basması sonucu oluşan Click isimli bir olay mevcuttur. Oysaki olayların her zaman için görsel ortamda olması ve işletim sistemi tarafından algılanacak bir etkileşime karşılık olarak uygulama ortamına fırlatılması şart değildir. Dolayısıyla olayları kavrayabilmenin en güzel yolu, geliştirici tarafından yazılan tiplere özel olarak nasıl yazılacağını ve kullanılacağını bilmekle mümkün olabilir. İşte bu makalemizde kendi tiplerimiz için özel olayları nasıl yazabileceğimizi incelemeye başlayacak ve olayları daha derinlemesine kavramaya çalışacağız.

Öncelikli olarak düğme kontrolü örneğinden sıyrılıp farklı vakkalar düşünerek ilerlemeye çalışalım. Örnek olarak stoktaki ürün bilgilerini içeren basit bir sınıf göz önüne alınabilir. Ürünlerin stoktaki değerlerininde bu tip içerisinde bir özellik yardımıyla sarmalanmış(wrap) olduğunu düşünebiliriz. Buna göre stok miktarının belirli bir değerin altına düşmesi sonrasında başka nesne örnekleri tarafından ele alınabilecek bir olay tanımlaması yapılabilir. Burada Urun tipi içerisinde tanımlanan ve bu tipe ait nesne örneklerinin ele alabileceği bir olay söz konusudur.

Başka bir örnek daha. Herhangibir bir kargo şirketinin ulaştırma filosundaki araçların programatik ortamda birer nesne ile ifade edilebildiği bir kütüphane(library) olduğunu düşünelim. Bu kütüphane içerisindeki fonksiyonelliklerden biriside, araçların uydu sistemleri yardımıyla düzenli olarak izlenmesi ve güncel koordinat, anlık hız gibi bilgilerinin elde edilmesi olarak göz önüne alınabilir. Bu hizmeti sağlayan kodlar ayrı bir kütüphane olacak şekilde geliştirilmiş olarak ticari bir paket halinde sunulabilir. O halde araçların belirlenen hız limitlerini aşmaları sonrasında oluşacak durumların söz konusu kütüphaneyi kullanan uygulamalar tarafından, istenirse ele alınmalarını sağlamak amacıyla olaylar yazılabilir. Böylece söz konusu program, araç hız limitini aştığı zaman neler yapmak istiyorsa bunları istediği şekilde ele alabilecektir.

Temel olarak kendi tiplerimiz için olay tanımlamak aslında temsilcileri (delegates) daha kolay kapsüllenmiş bir halde sunmak şeklinde de yorumlanabilir. Bir olayın(event) tanımlanabilmesi için mutlaka bir temsilci tipi ile eşleştirilmesi gerekmektedir.

NOT : Temsilciler(delegates) çok kanallı programlamada (multi threading), asenkron(asynchronous) mimarilerde(Polling, Callback, WaitHandle gibi) ve son olarak olay tabanlı(event based) kodlamada kullanılmaktadır.

Temsilci dışında dikkat edilmesi gereken bir diğer noktada olayın bir şekilde ortama fırlatılmasını sağlamaktır. Düğme örneğini burada göz önüne alabiliriz. Dikkat ederseniz bir düğmeye basıldığında gerçekleştirilmek istenenleri yazmak için tek yapılan oluşan olay metodunun içeriğini doldurmaktan ibarettir. Sistem arka tarafta söz konusu Button nesne örneği için bir olay yüklemsi yapmaktadır. Peki düğmeye basıldığında söz konusu olay metodu nasıl çağırılacaktır? (Burada temsilcinin rolünün ne kadar önemli olduğu ortadadır.) Olayın tetiklenmesi işletim sistemi tarafından gerçekleştirilir. Aslında Button nesne örneğinin arka planda yaptıklarından biriside, işletim sistemindeki bu aksiyonu yakalamaktır. Sonuç itibariyle kullanıcının söz konusu Button nesne örneğine yükleme yaptığı olay metodu çağırılır. Bu anlatılanlar bize şunu ifade etmelidir. Kendi olaylarımızı tanımlıyorsak, söz konusu olayın diğer nesneler tarafından ele alınabilmesini sağlamak için manuel olarak tetiklemeliyiz. Manuel olarak yapılan bu tetiklemenin sonucunda çalışma zamanında ele alınacak olay metodunun işaret edilmesini ise, temsilciler(delegates) yardımıyla sağlamalıyız.

NOT : Kendi tiplerimiz için olay tanımlıyorsak bu olayın çalışma zamanında diğer bir nesne tarafından ele alınabilmesi için bir şekilde tetiklenmesi gerekmektedir.

Aslında bir olayın tetiklenmesi, bir istisna(exception) nesne örneğinin ortama fırlatılmasına(throw) benzetilebilir. Tek fark ortama fırlatılan istisnaların catch blokları ile yakalanabiliyor olmasıdır. Olaylarda durum farklıdır. Ortada bir catch bloğu yoktur. Bunun yerine bir abone (subscriber) vardır. Basit olarak olayın tetiklenmesi sonucu çalıştırılacak olay metodunun bulunduğu nesne örneğini abone olarak düşünebiliriz. Bir başka deyişle olayı yakalayıp değerlendirecek olan nesne, olayın sahibi olan nesnenin ilgili olayına(event) abone olmaktadır. Dolayısıyla olayı tanımlayan ve tetikleyen nesneyi yayımcı (publisher) olarakda göz önüne alabiliriz. Aslında bahsettiğimiz kavramları daha kolay anlayabilmek amacıyla aşağıdaki grafiği incelemekte fayda vardır.

Birinci adıma göre Program nesnesi Urun tipine ait bir nesne örneği oluşturur. Program nesne örneğinden kasıt aslında uygulamanın ta kendisi olabilir. Örneğin bir konsol uygulamasındaki Program sınıfı veya windows uygulamasındaki bir Form nesne örneği olabilir. Hangisi olursa olsun değişmez gerçek olaya abone olmak isteyen bir nesnenin olmasıdır. Olay tanımlamamızın Urun tipi içerisinde olduğu varsayılırsa, Program nesnesinin ilgili olay metodunu Urun nesnesine abone etmesi gerekmektedir. Bu ikinci adımda sembolize edilmeye çalışılmaktadır.

Nesne kullanıcısı (Object User), Program nesnesi içerisinde olay yüklemesi ile birlikte bir olay metodunuda yazar. Böylece Urun nesnesinin üçüncü adımda yapacağı tetikleme sonucunda Program nesnesi tarafından yazılan olay metodu çağırılabilecektir. Burada söz konusu olan abone etme işlemi aslında Urun sınıfına ait olayın(event) += operatörü yardımıyla Program nesne örneği içerisinde yüklenmesidir. Olaylar tanımlanırken hep bir temsilci(delegate) tipi yardımıyla oluşturulurlar. Dolayısıyla Program sınıfı içerisinde Urun nesnesi için ilgili olay += operatör ile yüklendiğinde, temsilciye(delegate) parametre olarak verilen metod referansı Urun nesnesine bildirilir. Böylece Urun nesne örneği içerisinde ilgili olay tetiklendiğinde hangi metodun çağırılacağı bilinmektedir.

Bu kadar teorik bilgiyle aslında, olayların gerçek anlamda sadece button ve click kelimelerinden ibaret olmadığını göstermeye çalıştık. Artık birazda pratik yaparak bahsedilenleri örneklemekte fayda olacağı kanısındayım. Bu amaçla basit bir console uygulamasını göz önüne alacağız. Konsol uygulamamız içerisinde yer alan Program sınıfımız abonemiz(subscriber) olacak. Urun sınıfı içerisinde tanımlayacağımız olay(Event), stoktaki ürün sayısı 10 değerinin altına düştüğünde tetiklenecek şekilde tasarlanacaktır. Bir olay tanımlanırken mutlaka bir temsilcinin olması gerektiğinden bahsetmiştik. Dolayısıyla birde temsilci (delegate) tipi geliştirmemiz gerekecektir. Söz konusu temsilci tipini ve Urun sınıfını aşağıdaki gibi tasarlayabiliriz. (Örneklerimizde sadece olay kavramına yoğunlaşmak istediğimizden, yapılması gereken pek çok kontrol ortadan kaldırılmıştır. Örneğin ürün adının boş geçilmesini, StokMiktari veya BirimFiyat özelliklerine sıfırın altında değer atanmasını engellemek gibi. Daha pek çok kontrol ve fonksiyonellik düşünülebilir elbette. Siz kendi uygulamalarınızda bu noktaları sakın gözden kaçırmayın ve mutlaka uygulayın.)

using System;

namespace Olaylar
{
    delegate void StokAzaldiEventHandler();

    class Urun
    {
        private int id;
        private string ad;
        private double birimFiyat;
        private int stokMiktari;

        public event StokAzaldiEventHandler StokAzaldi;
   
        public int StokMiktari
        {
            get { return stokMiktari; }
            set {
                    stokMiktari = value;
                    if (value < 10
                        && StokAzaldi != null)
                            StokAzaldi();
                }
        }

        public double BirimFiyat
        {
            get { return birimFiyat; }
            set { birimFiyat = value; }
        }

        public string Ad
        {
            get { return ad; }
            set { ad = value; }
        }

        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        public Urun(int idsi, string adi, double fiyati, int stokSayisi)
        {
            Id = idsi;
            Ad = adi;
            BirimFiyat = fiyati;
            StokMiktari = stokSayisi;
        }
    }
}

Şimdi kodlarımızda neler yaptığımıza kısaca bakalım. İlk olarak StokAzaldiHandler tipinden bir temsilci (delegate) tanımlıyoruz. Hatırlayacağınız gibi zaman zaman .Net içerisinde var olan isimlendirme standartlarından bahsediyoruz. Niteliklere ait sınıf adlarının Attribute kelimesi ile, istisna(exception) tiplerinin Exception kelimesi ile bittiklerini biliyoruz. Özellikle olaylar ile ilişkili temsilcilerinde çoğunlukla EventHandler kelimesi ile bittiğini görürüz. Bu nedenle olay ile ilişkili temsilcimizi StokAzaldiEventHandler olarak isimlendirdik. StokAzaldiEventHandler isimli temsilci tipi, geriye değer döndürmeyen ve parametre almayan metodları işaret edebilecek şekilde tasarlanmıştır.

NOT : Temsilcilerin (delegate) çalışma zamanında metodların başlangıç adreslerini işaret ettiklerini ve işaret edebileceği metodun parametrik yapısı ile geri dönüş tipini belirtiklerini hatırlayalım.

Gelelim Urun sınıfımıza. Urun sınıf içerisinde UrunAzaldi isimli bir olay(event) tanımlanmıştır.

public event StokAzaldiEventHandler StokAzaldi;

Dikkat edilecek olursa event anahtar kelimesinden sonra StokAzaldiEventHandler isimli temsilci tipi gelmektedir. Son olarakta olayın adı yer alır. Böylece söz konusu olay için çalıştırılabilecek olay metodlarının yapısını StokAzaldiEventHandler isimli temsilcinin söyleyeceğide belirtilmiş olur. Geriye kalan tek pürüz, ilgili olay metodun nasıl ve nerede tetikleneceğidir. Örnek olması açısından StokMiktari isimli özelliğin set bloğunda aşağıdaki kod parçası kullanılmıştır.

set {
    stokMiktari = value;
    if (value < 10
        && StokAzaldi != null)
           StokAzaldi();
    }

Burada stok miktarı eğer 10 rakamının altındaysa ve StokAzaldi olayı null değere eşit değilse StokAzaldi() isimli bir metod çağrısı yapılmaktadır. StokAzaldi olayının null olmaması bir şekilde += operatörü ile yüklendiği anlamına gelmektedir. Yani başka bir nesne bu olaya kendisini abone(subscribe to) etmiştir. Bu durumda söz konusu olay metodunun buradaki set bloğu içerisinden çağırılması gerekir. Bu iş için yine olayı sanki bir metodmuş gibi çağırmak yeterli olacaktır. Nitekim bu çağrı += operatörü ile bağlanan olay metodunun yürütülmeye başlanması anlamınada gelmektedir. Buraya kadar += operatörü ile olayın yüklenmesi gerektiğinden bahsedip durduk. Peki bu nasıl gerçekleştiriliyor? Cevap aşağıdaki kod parçasında olduğu gibidir.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Olaylar
{
    class Program
    {
        static void Main(string[] args)
        {
            Urun ciklet = new Urun(10001, "Tipitipitip", 1.20, 35);
            ciklet.StokAzaldi += new StokAzaldiEventHandler(ciklet_StokAzaldi);

            for (int i = 0; i <5; i++)
            {
                ciklet.StokMiktari -= 7;
                Thread.Sleep(600);
                Console.WriteLine(ciklet.Ad + " için stok miktarı " + ciklet.StokMiktari.ToString());
            }
        }

        static void ciklet_StokAzaldi()
        {
            Console.WriteLine("Stok miktarı 10 değerinin altında...Alarrrmmm!");
        }
    }
}

Main metodu içerisinde Urun sınıfına ait bir nesne örneklendikten sonra StokAzaldi isimli olay, += operatörü ile yüklenmektedir. Burada Visual Studio kullanılıyorsa += işaretinden sonra iki kez tab tuşuna basmak yeterli olacaktır. Bu durumda Visual Studio otomatik olarak bir olay metodu oluşturacaktır. Örneğimizde bu olay metodu ciklet_StokAzaldi adıyla anılmaktadır.

NOT : Aslında bir olay tanımlandığında, bu olayın sahibi olan tip için CIL (Common Intermediate Language) kısmınada add_OlayAdı ve remove_OlayAdı isimli iki metod eklenir. Bu metodlar içerisinde olay yüklemesi yapıldığında veya çıkartıldığında, gereken temsilci bağlama ve ayırma işlemleri yapılmaktadır.

Program kodu içerisinde test amacıyla StokMiktari özelliğinin değeri 7şer 7şer azaltılmaktadır. Sonuçta ekran çıktısı aşağıdaki gibi olacaktır.

Dikkat edilecek olursa StokMiktari özelliğinin 10 değerinin altında olduğu her durum için otomatik olarak olay metodu tetiklenmiş ve içerisinde yazılan kod parçaları çalıştırılmıştır.

Yazılan olay her ne kadar faydalı görünsede bazı eksiklikleri olduğu ortadadır. Örneğin olay metodu içerisinde bir de stoğun o anki miktarının ne olduğunu öğrenebilsek fena olmaz mıydı acaba? Yada birden fazla Urun nesne örneğini aynı olay metoduna bağlayacaksak (ki bu mümkündür), olay metodu içerisinde hangi Urun nesne örneğinin ilgili olayın sahibi olduğunu tespit edebilsek fena olmaz mıydı? İşte bu iki gereksinime benzer ihtiyaçlar, .Net içerisinde var olan tüm olaylar içinde geçerlidir. O nedenle önceden tanımlanmış olan tüm olaylar aslında standart olarak iki parametre almaktadır. İlk parametre olayı tetikleyen nesne örneğine ait referansın yakalanması için kullanılırken, ikinci parametre olay metodu içerisine bilgi aktarmak maksadıyla ele alınır. Bu standart bir olay temsilcisinin işaret edeceği metodun parametrik yapısıdır. Tahmin edileceği gibi ilk parametre object tipindendir. İkinci parametre ise genellikle EventArgs gibi kelimeler ile biten özel bir sınıftır. Kendi örneğimizi göz önüne aldığımızda öncelikli olarak, olay metoduna özel bilgilerin aktarılmasını sağlayacak şekilde bir tipin geliştirilmesi gerekmektedir.

class StokAzaldiEventArgs:EventArgs
{
    private int guncelStokMiktari;

    public int GuncelStokMiktari
    {
        get { return guncelStokMiktari; }
        set { guncelStokMiktari = value; }
    }
    public StokAzaldiEventArgs(int gStk)
    {
        GuncelStokMiktari = gStk;
    }
}

StokAzaldiEventArgs isimli tipin tek yaptığı, güncel stok miktarını ilgili olay metodu içerisine taşımaktır. Bu tip asıl StokAzaldi olayı için anlamlıdır. Olay argümanlarını taşıyacak kendi tiplerimizi geliştirdiğimizde bunların EventArgs tipinden türetilmesi bir zorunluluk değildir ancak bir gelenektir. Amaç aynen isimlendirme kurallarında olduğu gibi kodun standardize edilmesidir. Nitekim .Net içerisindeki tüm olay argüman tipleri, bir şekilde EventArgs sınıfından türemektedir. Bu şekilde olay metoduna bilgi taşıyabileceğimiz bir tip tanımladıktan sonra temsilcininde aşağıdaki gibi değiştirilmesi gerekmektedir.

delegate void StokAzaldiEventHandler(object sender,StokAzaldiEventArgs args);

Elbette temsilcide yapılan değişikliklerin olayın tetiklendiği yerede adapte edilmesi gerekmektedir. Bu amaçla StokMiktari özelliğinin set bloğu aşağıdaki gibi değiştirilmelidir.

public int StokMiktari
{
    get { return stokMiktari; }
    set {
        stokMiktari = value;
        if (value < 10
            && StokAzaldi != null)
                StokAzaldi(this, new StokAzaldiEventArgs(value));
    }
}

Dikkat edilecek olursa ilk parametreye this anahtar kelimesi getirilmiştir. Hatırlayacağınız gibi ilk parametre için, olayı tetikleyen nesne referansını taşıdığını belirtmiştik. İşte buradaki this anahtar kelimesi çalışma zamanındaki(run-time) nesne referansının alınıp ilgili olay metoduna gönderilmesini sağlamaktadır. İkinci parametrede ise StokAzaldiEventArgs tipinden bir nesne örneği oluşturulmakta ve güncel stok miktarının değeri value anahtar kelimesi ile yapıcı metoduna gönderilmektedir. Tahmin edeceğiniz üzere nesne örneğide, olay metodu içerisinde ele alınabilecektir. Artık program içerisinde kodlarımızıda aşağıdaki gibi düzenlememiz gerekmektedir.

class Program
{
    static void Main(string[] args)
    {
        Urun ciklet = new Urun(10001, "Tipitipitip", 1.20, 35);
        ciklet.StokAzaldi += new StokAzaldiEventHandler(ciklet_StokAzaldi);

        for (int i = 0; i <5; i++)
        {
            ciklet.StokMiktari -= 7;
            Thread.Sleep(600);
            Console.WriteLine(ciklet.Ad + " için stok miktarı " + ciklet.StokMiktari.ToString());
        }
    }

    static void ciklet_StokAzaldi(object sender, StokAzaldiEventArgs args)
    {

        Console.WriteLine("Güncel stok değeri {0} . Stokta limit altına inilmiştir. Alarrmmmm!",args.GuncelStokMiktari.ToString());
    }
}

Uygulamayı bu haliyle çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ederiz.

Gelelim olaylar ile ilgili bir başka konuya. Daha öncede birden fazla nesne için aynı olay metodunun ele alınabileceğinden bahsetmiştik. Örneğimizi göz önüne aldığımızda, birden fazla Urun nesnesini aynı StokAzaldi olay metoduna yönlendirme şansına sahibiz. Bu şekilde bir ihtiyaç özellikle dinamik olarak oluşturulan kontrollerin bir olay metoduna bağlanarak ele alınması gibi durumlarda kullanılmaktadır. Ki böylece bir den fazla olay metodunu düşünmek yerine tek bir merkez metoddan kontrol ve yönetim işlemlerini gerçekleştirebiliriz. Örneğimizdeki kod parçalarını aşağıdaki gibi değiştirelim.

class Program
{
    static void Main(string[] args)
    {
        Urun ciklet = new Urun(10001, "Tipitipitip", 1.20, 35);
        Urun cikolata = new Urun(10034, "Marsi", 2.5, 25);
        cikolata.StokAzaldi+=new StokAzaldiEventHandler(urun_StokAzaldi);
        ciklet.StokAzaldi += new StokAzaldiEventHandler(urun_StokAzaldi);

        for (int i = 0; i <5; i++)
        {
            ciklet.StokMiktari -= 7;
            cikolata.StokMiktari -= 5;
            Thread.Sleep(600);
            Console.WriteLine(ciklet.Ad + " için stok miktarı " + ciklet.StokMiktari.ToString());
            Console.WriteLine(cikolata.Ad + " için stok miktarı " + cikolata.StokMiktari.ToString());
        }
    }

    static void urun_StokAzaldi(object sender, StokAzaldiEventArgs args)
    {
        Urun urn = (Urun)sender;
        Console.WriteLine("{0} için güncel stok değeri {1} . Stokta limit altına inilmiştir. Alarrmmmm!",urn.Ad,args.GuncelStokMiktari.ToString());
    }
}

İlk olarak ciklet ve cikolata isimli iki ayrı Urun nesnesi örneklediğimize ama bunların her ikisi içinde aynı olay metodunu kullandığımıza dikkat edelim.

cikolata.StokAzaldi+=new StokAzaldiEventHandler(urun_StokAzaldi);
ciklet.StokAzaldi += new StokAzaldiEventHandler(urun_StokAzaldi);

Dikkat edilmesi gereken önemli noktalardan biriside olay metodu içerisinde sender isimli parametre değişkeninin nasıl kullanıldığıdır. sender isimli değişken cast işlemine tabi tutularak bir Urun nesne örneğine dönüştürülmekte ve kullanılmaktadır. Burada elbetteki akla şu soru gelebilir. Urun nesne örneğine dönüştürme işlemi yapıldıktan sonra güncel stok miktarı gibi verilerde elde edebilir bu nedenle StokAzaldiEventArgs gibi tipleri geliştirmeye ihtiyacımız var mıdır? Aslında bu bir anlamda doğru olsada bir argüman tipinin var olması, olay metodu içerisine gerçektende ne aktarmak istediğimizi belirten bir kodlama yolu ve standardı sağlamaktadır. Diğer taraftan gereksiz cast işlemlerininde önüne geçilmiş olacaktır. Bunların dışında nesne örneği üzerinden elde edilemeyen ancak argümanlar yardımıyla ele alınabilecek bazı verilerin yayıncı nesne(publisher object) içerisinden sadece olay metoduna aktarılmasıda sağlanabilir. Bir gerekçe daha vardırki o da biraz sonra generic bir temsilci ile karşımıza çıkacaktır.

C# 2.0 ile gelen yeniliklerden biriside isimsiz metodlardır (anonymous methods). Bu kavram özellikle temsilcilerin içerisinde rol aldıgı kodlama alanlarında kullanılmaktadır. Dolayısıyla olay metodlarınıda isimsiz olarak tanımlama ve geliştirme şansına sahibiz. Yani yukarıdaki kodlarımızı aşağıdaki gibi geliştirebiliriz.

static void Main(string[] args)
{
    Urun ciklet = new Urun(10001, "Tipitipitip", 1.20, 35);
    ciklet.StokAzaldi += delegate(object sender, StokAzaldiEventArgs arg)
                                {
                                    Urun urn = (Urun)sender;
                                    Console.WriteLine("{0} için güncel stok değeri {1} . Stokta limit altındayız. Alarrmmmm!", urn.Ad, arg.GuncelStokMiktari.ToString());
                                };

    for (int i = 0; i <5; i++)
    {
        ciklet.StokMiktari -= 7;
        Thread.Sleep(600);
        Console.WriteLine(ciklet.Ad + " için stok miktarı " + ciklet.StokMiktari.ToString());
    }
}

Programı çalıştırdığımızda yine olay metodunun başarılı bir şekilde işletildiğini görebiliriz. Burada dikkat edilecek olursa StokAzaldi olayı(event) yüklenirken isimsiz metod(anonymous method) kullanılmıştır. delegate anahtar kelimesi otomatik olarak StokAzaldiEventHandler temsilcisine(delegate) bürünmektedir. Sonrasında gelen kod bloğu içerisinde ise olay metodunda yapılması gerekenler yer almaktadır. Ortada bir olay metodu adı olmadığına dikkat edelim. Buda zaten neden isimsiz metod denildiğini açıklamaktadır.

C# 2.0 ile birlikte gelen özelliklerden birisi ve belkide en önemliside generic mimaridir. Bildiğiniz gibi generic mimari sayesinde tür bağımsız tipler geliştirebilme şansına sahibiz. .Net içerisinde var olan pek çok tipin bu şekilde tür bağımsız versiyonları geliştirilmiş ve tip güvenli ile performans gibi konularda daha güçlü tipler ortaya çıkmıştır. Olaylarla ilişkili olaraktan, EventHandler isimli standart temsilci(delegate) tipinin generic bir versiyonu vardır. Söz konusu temsilcinin prototipi aşağıdaki gibidir.

[SerializableAttribute]
public delegate void EventHandler<TEventArgs> (Object sender,TEventArgs e) where TEventArgs : EventArgs

Buna göre kendi olaylarımız için ayrıca temsilci yazmaya gerek kalmamaktadır. Dikkat edilecek olursa TEventArgs isimli generic türün yazılan kısıtlama(constraint) sayesinde EventArgs tipinden türemiş bir tip olması beklenmektedir. Buna göre Urun tipi içerisindeki olay tanımlamasını aşağıdaki gibi değiştirebiliriz.

public event EventHandler<StokAzaldiEventArgs> StokAzaldi;

EventHandler<TEventArgs> temsilcisi ilk parametre olarak object tipinden bir değişken almaktadır. İkinci parametre ise EventArgs sınıfından türemiş bir tiptir. Bizim örneğimizde söz konusu tip StokAzaldiEventArgs sınıfıdır. (Sanırım kendi olay argüman tiplerimizi EventArgs sınıfından türetmenin bir faydasını daha görmüş oluyoruz.) Sonuç olarak uygulamanın çalışması değişmeyecektir. Kazancımız ekstradan temsilciler tasarlanmasına gerek kalmayışıdır. Tabi isimsiz bir metod kullanmıyorsak olayın yükleniş şeklinide aşağıdaki gibi değiştirmemiz gerekecektir.

ciklet.StokAzaldi += new EventHandler<StokAzaldiEventArgs>(urun_StokAzaldi);

Olaylar ile ilgili olarak bilmemiz gereken bir diğer noktada; += ile yüklenen olayların -= ile kaldırılabildiği ve her iki durumunda add ve remove isimli bloklar içerisinde kontrol altına alınabildiğidir. Bu konunun incelemesinide siz değerli okurlarıma bırakıyorum. Böylece geldik bir makalemizin daha sonuna. Bu makalemizde event kavramını daha detaylı bir şekilde incelemeye çalıştık ve kendi olaylarımızı nasıl yazabileceğimizi gördük. Temel olarak işlediklerimizi aşağıdaki maddeler ile özetleyebiliriz.

  • Olaylar temsilcilerin(delegates) özelleştirilmiş bir hali olarak düşünülebilir.
  • Bir olay tanımlandığında mutlaka bir temsilci tipi ile eşleştirilir. Nitekim olay meydana geldiğinde çağırılacak metodun, birisi tarafından çalışma zamanında(runtime) işaret ediliyor olması gerekir ki bunu temsilciler yapabilir.
  • Kendi olaylarımızı geliştirirken isimlendirme standardı açısından temsilcilerimizi EventHandler, olay argümanlarını taşıyacak sınıflarımızı EventArgs kelimeleri ile bitirmekte fayda vardır.
  • Olayın tanımlı olduğu nesne tarafından tetiklenmesi sonrasında yakalanabilmesi için, söz konusu nesneyi kullanan diğer nesnenin(subscriber) olaya abone olması gerekmektedir.
  • Birden fazla nesne olayını aynı olay metoduna bağlayabiliriz.
  • Olay metodlarını işaret edecek temsilciler, ilk parametre olarak olayı meydana getiren nesne referansını taşıyan object bir değişken, ikinci parametre olarakta olay metoduna bilgi taşıyacak bir sınıf örneğini alan metodları işaret edecek biçimde tasarlanırlar.
  • Olay metodlarını yazmak zorunlu değildir. Bunun yerine isimsiz metodlarda(anonymous methods) kullanılabilir.
  • İstenirse kendi olaylarımız için temsilci yazmak yerine EventHandler<TEventArgs> generic tipi kullanılabilir.
  • Olaylara argüman taşıyacak tiplerimizi hem kod standardı hemde EventHandler<TEventArgs> desteği için EventArgs sınıfından türetmekte fayda vardır.
  • Olayların += operatörü ile yüklenmesi ve -= operatörü ile kaldırılması durumlarını kontrol altına almak için add ve remove bloklarından faydalanılabilir.

Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama İçin Tıklayınız.

Tags:   ,
Categories:   C#
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

C# Temelleri - Nitelikleri(Attributes) Kavramak

Çarşamba, 11 Nisan 2007 20:39 by bsenyurt

Nitelik(Attribute) eninde sonunda her dotNet programcısının kullandığı ve karşılaştığı bir kavramdır. Özellikle yansıma (Reflection) konusu ile birlikte anıldığından, .Net Framework içerisinde önemli bir yere sahiptir. .Net Framework içerisinde pek çok modelde niteliklerden aktif olarak faydalanılmaktadır. Web servislerinden windows kontrollerini geliştirmeye, kendi web part bileşenlerimizi yazmaktan serileştirmeye kadar pek çok alanda işe yaramaktadır. Hatta çok popüler olarak, katmanlı mimarilerde ve nitelik bazlı (attribute based) programlama modellerinde de ele alınmaktadır. İşte bu makalemizde nitelikleri incelemeye çalışacak ve özellikle kendi niteliklerimizi nasıl geliştirebileceğimize değineceğiz.

Herşeyden önce, niteliği (Attribute) tanımlamakta fayda vardır. Nitelikler, uygulandıkları tiplerin (types) yada üyelerin (members) çalışma zamanındaki davranışlarının değiştirilmesine olanak sağlayan sınıflardır. Niteliklerin sınıf(class) olduğu rahatlıkla söylenebilir. Nitekim var olan veya bizim tarafımızdan geliştirilen nitelikler daima Attribute sınıfından türemek zorundadırlar. Attribute, abstract bir sınıftır. Dolayısıyla örneklenemez ancak bir nitelik sınıfının içermesi gereken temel üyeleri bünyesinde barındırır. Aslında niteliklerin belkide en önemli özelliği, üretilen assembly içerisinde yer alan tip ve üyelere ekstra bilgiler katabilmeleridir. Bir başka deyişle metadata içerisine ilave bilgiler eklenebilmesini sağlamaktadır. Bu noktada ortaya önemli bir soru çıkar. Söz konusu ekstra veriler kim tarafından ve nasıl değerlendirilecektir? İşte bu noktada yansıma(Reflection) konusu çok büyük önem taşımaktadır. Öyleki, çalışma zamanında(run-time) herhangibir tipin ve üyelerinin hakkında bilgi sahibi olabilme imkanı aynı zamanda metadata içeriğinide elde edebilme anlamına gelmektedir.

NOT : Nitelikler(Attributes), .Net Framework' de var olan veya geliştiriciler tarafından yazılan tip(type) veya üyelere(members) çalışma zamanında davranışlarının farklı şekillerde ele alınabilmelerini sağlayan ekstra metadata (veri hakkında veri) bilgileri ekler. Bu metadata bilgileri üretilen assembly' lar içerisinde yer alır ve yansıma(Reflection) teknikleri ile çalışma zamanında değerlendirilebilir.

Niteliklerin faydasını ve ne işe yaradıklarını daha net bir şekilde anlayabilmek için aşağıdaki örnek senaryolar göz önüne alınabilir.

Asp.Net Web Uygulamalarında Kendi Kontrollerimizi Geliştirirken

Daha önceki makalelerimizde kendi web server kontrollerimizi nasıl yazacağımıza kısaca değinmiştik. Şimdi şöyle düşünelim. Yazdığımız bu kontrollerin ele alındığı bir geliştirme ortamı var mı? Cevabın Visual Studio IDE ortamı olduğunu gayet iyi biliyoruz. Peki nasıl oluyorda, bir kontrolü ToolBar üzerinden alıp sayfaya bıraktığımızda, özellikler (Properties) pencersinde, o kontrol sınıfına ait bazı üyeler (özellikler, olaylar) getiriliyor? Demekki, IDE bir çalışma zamanı ortamı olarak sürüklenip bırakılan kontrolün hangi üyelerinin Properties penceresinde görünmesi gerektiğini anlayabiliyor. Hatırlayacağınız gibi özelliklerin başına, hatta kontrol sınıfının başına atılan bazı nitelikler(attributes) vardı.

[Browsable(true)]
[Description("Hangi Gün?")]
[Bindable(true)]
[Themeable(true)]
[Category("Tarih Degerleri")]
[DefaultValue("1")]
[Localizable(true)]

public string SeciliGun
{

Çok basit olarak SeciliGun isimli özelliğin üzerine yazılmış olan nitelikler, Visual Studio IDE' si için anlamlıdır. Nitekim Visual Studio çalışan bir uygulama olaraktan, çalışma zamanında (run-time) ilgili niteliklerin değerlerine bakarak bazı hamlelerde bulunur. Örneğin Browsable niteliğinin true değerine sahip olması, SeciliGun özelliğinin Visual Studio IDE' sinde Properties penceresine eklenmesi gerektiği anlamına gelir. Description niteliği içerisindeki metinsel bilgiler, IDE tarafından değerlendirilip yine Properties penceresinde gösterilir. (Diğer niteliklerin ne amaçla kullanıldıklarını  Web Server Control Yazmak - 2 isimli makaleden bulabilirsiniz.)

Kendi Web Part Bileşenlerimizi Geliştirdiğimizde

Bundan bir önceki makalemizde kendi Web Part bileşenlerimizi nasıl geliştirebileceğimiz incelemiştik. Geliştirdiğimiz Web Part bileşenlerinin bazı özelliklerinin kişiselleştirilebilmesi(personalizable) ve çalışma zamanında istemcinin bilgisayarında yer alan tarayıcı pencersindeki bir PropertyGridEditorPart içerisinde açılıp değiştirilebilmesi için aşağıdaki niteliklerden faydalandık.

[WebBrowsable(true)]
[WebDescription("Verilen Url adresine göre Rss bilgisini okur")]
[Personalizable(PersonalizationScope.User)]
[WebDisplayName("Rss Bilgisi Alınacak Url")]
public string Url
{
    get { return _Url; }
    set { _Url = value; }
}

İşte buradaki nitelikleri değerlendiren kişi, Asp.Net Runtime Host' un ta kendisidir. Yine çalışma zamanındaki bir ortamın karar mekanizmalarında ihtiyaç duyacağı bazı bilgiler metadata içerisine nitelikler yardımıyla eklenmektedir. Buna göre örneğin, Asp.Net Runtime Host, Personalizable niteliğinde PersonalizationScope isim enum sabitinin değerini User olarak gördüğünde takip eden özelliğin kişiselleştirme amaçlı olarak her kullanıcı için ayrı olacak şekilde veritabanına yazılması gerektiğini anlayacaktır. Yine WebBrowsable niteliğine true değeri verilmesi sayesinde, ilgili özelliğinde istemcilerin tarayıcı penceresinde görülecek olan PropertyGridEditorPart içerisinde ele alınabileceğinide anlayacak ve sayfanın sunucundan istemciye olan hareketinde, render işlemini bu kritere göre değiştirecektir. (Diğer niteliklerin ne yaptığı ile ilgili olaraktan Kendi Web Part Bileşenlerimizi Geliştirmek isimli makalemizden yararlanabilirsiniz.)

Nesneleri Binary Formatta Serileştirmekte

Bildiğiniz gibi bir nesneyi ikili (binary) formatta serileştirmek için BinaryFormatter sınıfının Serialize metodundan yararlanırız. Benzer şekilde ters serileştirme işlemi içinde Deserialize metodunu kullanırız. Ancak hepimizin yakından tanıdığı bir kural vardır. Bir tipin ikili formatta(binary) serileştirilebilmesi için Serializable niteliği ile işaretlenmiş olması gerekir. Binary formatta serileştirmenin olduğu yerler göz önüne alındığında söz konusu niteliğin önemi ortaya çıkmaktadır. Örneğin web uygulamalarında session bilgilerinin veritabanından tutulmasına karar verildiğinde veya Profile bilgilerinde kendi tiplerimizi yada var olan tipleri tablodaki binary alanda tutmak istediğimizde...

Windows Communication Foundation' da Kontratları(Contrats) Hazırlarken

Yakın zamanda, .Net Framework 3.0 ile gelen ve Microsoft tabanlı dağıtık mimari (distributed architectures) modellerini tek bir çatı altında toplayan Windows Communication Foundation' da, bir sınıfın servis olarak yayınlanması için ve sınıf içinden dış dünyaya açılabilecek fonksiyonellikler için yine nitelikleri kullanmaktadır. Aşağıdaki örnek kod parçasında ServiceContract ve OperationContract isimleriye geliştirilen niteliklerin örnek uygulanış şeklini görmektesiniz. (Daha detaylı bilgi için WCF Giriş makalesine bakabilirsiniz.)

[ServiceContract]
    public interface IMatematikServis
    {
        [OperationContract]
        double Toplam(double x, double y);

        void DahiliMetod();
    }

Web Servislerinde

Bir web servisinin istemci tarafından tüketilebilmesi için çoğunlukla proxy sınıflarını kullanıyoruz. Elbette istisnai olarak doğrudan HTTP veya SOAP üzerinden talepte de bulunabilmekteyiz. Nitekim proxy sınıflarının üretilebilmesi içinde, web servisine ait bir WSDL dökümanının ele alınması gerekiyor. WSDL (Web Service Description Language) dökümanı bildiğiniz gibi bir web servisinin tanımlamalarının ve fonksiyonelliklerin bir XML içeriği olarak üretilmesini sağlıyor. Peki biz bu belgeyi herhangibir şekilde talep ettiğimizde, bu talebe karşılık XML dökümanı içerisine hangi sınıfların ve hangi metodların koyulacağını sistem nereden biliyor? İşte bu noktada devreye WebService ve WebMethod gibi nitelikler(attributes) girmektedir. Böylece WSDL dökümanını hazırlayacak olan HttpHandler hangi sınıfı ve hangi metodu xml içerisine alacağını bilecektir.

Katmanlı Mimaride Entity Tiplerinde

Özellikle katmanlı mimaride nitelikler çok faydalı olabilmektedir. Örneğin, veritabanında yer alan tabloların karşılıklarının tutulduğu sınıflar için otomatik olarak select, insert,update ve delete gibi sorguların hazırlanması istendiği durumlarda çalışma zamanı için ekstra bilgilere (additional metadata) ihtiyaç vardır. İşte çalışma zamanındaki bu ihtiyçaları nitelikler yardımıyla karşılayabiliriz. Söz gelimi entity tipi içerisindeki alan adlarının tablolardaki karşılıklarını, identity olup olmadıklarını yada farklı entity tipleri arasında, tablolar arasındaki ilişkilerin nasıl gerçekleştirilebileceğini belirlemek vb konularda ele alınabilir.

DLINQ (Database Language Integrated Query) de Yer Alan Entity Tiplerinde

Şu anda C# 3.0 ile birlikte adı en çok anılan modellerden biriside DLINQ(Database Language Integrated Query) dir. DLINQ temel olarak veritabanından nesnelere indirgenen kümeler üzerinde LINQ sorgularının çalıştırılmasına izin veren bir modeldir. (Daha fazla bilgi için C# 3.0 İlk Bakışta DLINQ makalesine bakabilirsiniz) Aslında model tipik olarak entity katmanlarına dayanan bir yapıya sahiptir. Tablo, alan ve ilişki (relation) eşleştirmeleri vb... için niteliklerden (attributes) faydalanılmaktadır.

[Table(Name="Calisanlar")]
class Calisan
{
    [Column(Name="Id",Id=true)]
    public int Id;

Bir Assembly Hakkında Bilgi Vermek için AssemblyInfo.cs İçeriğinin Değişitirlmesi

Geliştirdiğimiz uygulamaların ürettiği assembly' lara ait genel bilgileri AssemblyInfo.cs dosyası içerisindeki assembly seviyesinde kullanılabilen nitelikler sayesinde metadata içerisine alabiliriz. Bu sayede geliştirdiğimiz Assembly' ın hangi kültüre destek verdiğini(Culture), versiyonunu(Version), varsa strong key bilgisini, başlığını (Title), açıklamasını (Description) belirtebiliriz. Bu tip bilgiler metadata içerisine alındıktan sonra örneğin ClickOnce gibi mimariler tarafından kullanılıp setup sayfalarının oluşturulması sırasında kullanılabilir.

Gördüğünüz gibi, nitelikler çalışma zamanında bir takım uygulama parçaları tarafından değerlendirilmekte ve buna göre sonuçlar üretilmektedir.

Bu kısa bilgilerden sonra gelin kendi niteliklerimizi (Custom Attributes) nasıl yazabileceğimize bakalım. Kendi niteliklerimizin kıymetlenebilmesi için onları ele alacak bir modelede ihtiyacımız olacaktır. Burada devreye yansıma(Reflection) girecek. Örneğin Sql Server 2005 ile birlikte gelen AdventureWorks veritabanındaki Production şemasında (Schema) yer alan Product tablosunun programımız içerisinde bir tip ile ifade edildiğini düşünebiliriz. Bu tip için gerekli insert, update, delete ve select işlemlerinin bu tip içerisindeki metodlar ile yapılmak istendiğini düşünelim. Bu durumda yansımadan faydalanarak özelliklerin adlarından ve o anki değerlerinden yararlanıp bizim için gereken sorguları otomatik olarak hazırlatabiliriz. Ancak dikkat edilmesi gereken noktalar vardır. Örneğin ProductId alanı identity tipindendir ve bu nedenlede otomatik olarak artmaktadır. Dolayısıyla otomatik oluşturulacak insert sorgusuna dahil edilmemesi gerekir. Peki çalışma zamanında bu alanı işaret eden sınıf özelliğinin, insert sorgusuna dahil edilmemesi gerektiğini nereden bilebiliriz? İşte bu özellikler için yazacağımız bir nitelik yardımıyla çalışma zamanında davranış değiştirilmesini sağlayabiliriz. Gelin ne demek istediğimiz örnek üzerinden incelemeye çalışalım. Bu amaçla öncelikli olarak bir Product nesnesini temsil edecek bir sınıf tasarlayacağız. Amacımız insert, update, delete ve select sorgularının çalışıp çalışmadığını kontrol etmekten ziyade, bunların oluşturulması sırasında niteliklerin değerini anlamak olduğundan sadece bir kaç temel özelliğin sınıfa dahil edildiğini hatırlatalım. UrunEntity isimli sınıfımızın genel tasarımı şu şekilde olacaktır.

Sınıfımız içerisindeki niteliklerin uygulanmasını ve metodlarımızı ilerleyen kısımlarda geliştireceğiz. Gelelim niteliklerimize. Makalemizin başındada belirttiğimiz gibi bir nitelik mutlaka Attribute sınıfından türemelidir ki metadata içerisine eklenebilsin. Bu nedenle sınıfımızın eşleştiği tablo ve kolonları için kullanılacak TabloAttribute ve AlanAttribute sınıflarını Attribute sınıfından türeterek geliştireceğiz.

NOT : İsimlendirme standartları oldukça önemlidir. Bu tüm geliştiricilerin aynı tarzda kodlama yapmasını ve kooridanasyon kolaylığını sağlar. Örneğin tüm Exception sınıflarının adları Exception kelimesi ile biter veya tüm arayüzlerin (interfaces) adları I harfi ile başlar. Benzer durum nitelikler içinde geçerlidir. Öyleki nitelik sınıflarının adlarıda Attribute kelimesi ile bitmektedir. Bu nedenle kendi niteliklerimizi isimlendirirken adlarının Attribute kelimesi ile bitmelerine özen gösterilmelidir.

Niteliklerimize ait sınıf diagramı ve kodlarımız ise aşağıdaki gibidir.

TabloAttribute.cs;

// TabloAttribute isimli niteliğimiz sadece sınıf veya yapılara uygulanabilecektir.
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct)]
class TabloAttribute:Attribute
{
    private string _tabloAdi;
    private string _schemaAdi;
 
    public string TabloAdi
    {
        get { return _tabloAdi; }
        set { _tabloAdi = value; }
    }
    public string SchemaAdi
    {
        get { return _schemaAdi; }
        set { _schemaAdi = value; }
    }
 
    public TabloAttribute(string tablonunAdi, string schemaninAdi)
    {
        TabloAdi = tablonunAdi;
        SchemaAdi = schemaninAdi;
    }
    public TabloAttribute(string tablonunAdi)
        : this(tablonunAdi, "dbo")
    {
    }
    public TabloAttribute()
    {   
    }
}

AlanAttribute;

[AttributeUsage(AttributeTargets.Property)]
class AlanAttribute:Attribute
{
    private string _alanAdi;
    private bool _identity;
    private bool _nullIcerebilir;

    public bool NullIcerebilir
    {
        get { return _nullIcerebilir; }
        set { _nullIcerebilir = value; }
    }

    public string AlanAdi
    {
        get { return _alanAdi; }
        set { _alanAdi = value; }
    }
    public bool Identity
    {
        get { return _identity; }
        set { _identity = value; }
    }

    public AlanAttribute(string alaninAdi, bool identityMi, bool nullIcerirmi)
    {
        AlanAdi = alaninAdi;
        Identity = identityMi;
        NullIcerebilir = nullIcerirmi;
    }
    public AlanAttribute(string alaninAdi, bool identityMi)
        : this(alaninAdi, identityMi, true)
    {
    }
    public AlanAttribute(string alaninAdi)
        : this(alaninAdi, false)
    {
    }
    public AlanAttribute()
    {   
    }
}

Şu andaki amacımız kendi niteliklerimizi nasıl yazacağımızı görmek olduğundan tam anlamıyla bir entity tipi oluşturmayı hedeflemiyoruz. Bu nedenle TabloAttribute isimli sınıfımız temel olarak eşleştirme amacıyla tablo adı ve bulunduğu şema adını taşıyacak özelliklere sahip. Benzer şekilde AlanAttribute isimli sınıfımızda alan adını, null değer taşınabilip taşınamıyacağını, alanın identity tipinde olup olmadığını belirten özellikler içermektedir. Gördüğünüz gibi Attribute' tan türettiğimiz sınıfların normal sınıflardan farklı bir yazım tarzı bulunmamaktadır. Ancak dikkat ederseniz yazmış olduğumuz nitelik sınıflarımıza AttributeUsage isimli başka bir nitelik daha uygulanmaktadır. Bu niteliğin amacı, ilgili niteliğin hangi seviyelere uygulanabileceğini belirlemektir. Bu seviylerin belirtilmesi içinse AttributeTargets isimli bir enum sabitini ele almaktadır. Örneğin TabloAttribute niteliğimizi sadece sınıf(class) ve yapılara(struct) uygulayabilirken, AlanAttribute isimli niteliğimizi sadece özelliklere(Property) uygulanabilir. Böylece ilgili niteliğin sadece belirtilen tip veya üyelere uygulanabilmesi adına bir zorlama getirilmiş olunur. AttributeTargets isimli enum sabitinin alabileceği tüm değerler ve kısa açıklamaları aşağıdaki tabloda görüldüğü gibidir.

Değer Açıklama
All Nitelik istenilen tipe veya üyeye uygulanabilir.
Assembly Nitelik sadece assembly seviyesinde uygulanabilir.
Class Nitelik sadece sınıflara uygulanabilir.
Constructor Nitelik sadece yapıcı metoda uygulanabilir.
Delegate Nitelik sadece temsilci tipine uygulanabilir.
Enum Nitelik sadece enum sabitine uygulanabilir.
Event Nitelik sadece olaya uygulanabilir.
Field Nitelik sadece alana uygulanabilir.
GenericParameter Nitelik sadece generic bir parametreye(T) uygulanabilir.
Interface Nitelik sadece arayüze uygulanabilir.
Method Nitelik sadece metoda uygulanabilir.
Module Nitelik sadece modül' e uygulanabilir. Burada dikkat edilmesi gereken nokta module' ün bir Visual Basic module' ü olmayışıdır. Yani kastedilen .dll veya .exe uzantılı module' lerdir.
Parameter Nitelik sadece parametreye uygulanabilir.
Property Nitelik sadece özelliğe uygulanabilir.
ReturnValue Nitelik sadece dönüş tipine uygulanabilir.
Struct Nitelik sadece bir değer türüne bir başka deyişle yapıya uygulanabilir.

Şimdi bu nitelikleri UrunEntitiy sınıfı içerisinde kullanmaya çalışalım. İlk olarak sınıfımızı aşağıdaki gibi geliştirelim.

[Tablo(SchemaAdi="Production",TabloAdi="Product")]
class UrunEntity
{
    private int _urunId;
    private decimal _fiyat;
    private string _urunAdi;
    private DateTime _sonSatisTarihi;

    [Alan(AlanAdi = "ProductID", Identity = true, NullIcerebilir = false)]
    public int UrunId
    {
        get { return _urunId; }
        set { _urunId = value; }
    }

    [Alan("Name", false, false)]
    public string UrunAdi
    {
        get { return _urunAdi; }
        set { _urunAdi = value; }
    }
    [Alan("ListPrice", Identity = false, NullIcerebilir = false)]
    public decimal Fiyat
    {
        get { return _fiyat; }
        set { _fiyat = value; }
    }

    [Alan("SellStartDate", false, true)]
    public DateTime SonSatisTarihi
    {
        get { return _sonSatisTarihi; }
        set { _sonSatisTarihi = value; }
    }

    public UrunEntity(int idsi, string adi, decimal fiyati)
    {
        UrunId = idsi;
        UrunAdi = adi;
        Fiyat = fiyati;
    }
    public UrunEntity()
    {
    }
}

Gördüğünüz gibi UrunEntity sınıfına ve UrunId,UrunAdi, Fiyat ve SonSatisTarihi isimli özelliklerimize TabloAttribute ve Alan Attribute niteliklerimiz uygulanmıştır. Buna göre UrunEntity sınıfının aslında Production şemasındaki Product tablosuna işaret ettiğini anlayabiliriz. Ya da, UrunId isimli özelliğin ProductId isimli alana işaret ettiğini, null değer içeremeyeceğini ve en önemliside Identity bir alan olduğunu anlayabiliriz. Böylece reflection tekniklerini kullanan kodlarımız insert sorgusunu oluştururken ProductId alanını hesabe katmayacağını anlayabilecektir. Elbette bunun geliştirici tarafından kodlanması gerektiğinide unutmayalım. Diğer özellikler içinde benzer uygulamalar yapılmıştır. AlanAttribute ve TabloAttribute isimli sınıflarımız içersinde birden fazla aşırı yüklenmiş yapıcı metod (constructor) kullandığımızdan, niteliklerimizi söz konusu üyelere farklı biçimlerde uygulayabiliriz. Bunlardan birisi Name=Value ataması şeklinde olan versiyondur. Bu versiyon doğrudan public olan özelliklere(Property) değer atanabilmesini sağlar. Yani özel olarak aşırı yüklenmiş yapıcı metodlar olmasada ilgili niteliğin özellikleri değiştirilebilir.

Artık bundan sonra niteliklerimizi ele alacağımız kodlarımızı yazmamız gerekmektedir. Bu basit kod parçası ile, çalışma zamanında var olan nitelikleride nasıl okuyabileceğimizi ve buna göre nasıl davranış değiştirebileceğimizi görmüş olacağız. Bu amaçla UrunEntity isimli sınıfımızın Insert metodunu aşağıdaki gibi geliştirelim.

public int Insert()
{
    Type tip = this.GetType();
    TabloAttribute tblAtr = ((TabloAttribute[])tip.GetCustomAttributes(typeof(TabloAttribute), false))[0];
    string tabloAdi=tblAtr.TabloAdi;
    string schemaAdi =tblAtr.SchemaAdi;
    StringBuilder insertBuilder = new StringBuilder();
    insertBuilder.Append("Insert into ");
    insertBuilder.Append(schemaAdi);
    insertBuilder.Append(".");
    insertBuilder.Append(tabloAdi);
    insertBuilder.Append(" (");

    // Insert sorgusundaki alan adları çekiliyor.
    foreach (PropertyInfo prp in tip.GetProperties())
    {
        AlanAttribute atr=((AlanAttribute[])prp.GetCustomAttributes(typeof(AlanAttribute), false))[0];
        if (!atr.Identity)
        {
            string alanAdi = atr.AlanAdi;
            insertBuilder.Append(alanAdi);
            insertBuilder.Append(",");
        }
    }
    // Son eklenen virgülü kaldırmak için.
    insertBuilder.Remove(insertBuilder.Length-1, 1);
    insertBuilder.Append(") Values (");
       
    // insert sorgusundaki değerleri çekiliyor.
    foreach (PropertyInfo prp in tip.GetProperties())
    {
        AlanAttribute atr=((AlanAttribute[])prp.GetCustomAttributes(typeof(AlanAttribute), false))[0];
        if (!atr.Identity)
        {
            object alanDegeri = prp.GetValue(this, null);
            if ((prp.PropertyType.Name == "String")
                || (prp.PropertyType.Name == "DateTime"))
                    insertBuilder.Append("'" + prp.GetValue(this, null).ToString() + "',");
            else
                insertBuilder.Append(prp.GetValue(this, null).ToString() + ",");
        }
    }
    insertBuilder.Remove(insertBuilder.Length - 1, 1);
    insertBuilder.Append(")");

    //Insert işlemi için gerekli sorgula çalıştırılır.

    return 0;
}

Kod parçası biraz arap saçına dönmüş olabilir. Gelin ne yaptığımıza ve nitelikleri çalışma zamanında nasıl ele alabildiğimize yakından bakalım. Insert metodunun amacı, UrunEntity sınıfı için gerekli olan insert sql sorgu cümlesini otomatik olarak oluşturmaktır. Bu işlemin çalışma zamanında yapılmasını hedeflediğimizden yoğun olarak reflection işlemleri kullanılmaktadır. Bununla birlikte otomatik olarak artan, bir başka deyişle identity tipinde olan bir alanın insert sorgusuna dahil edilmemesi gerekmektedir. Öyleyse bu bilgiyi içeren nitelik (attribute) ele alınmalıdır. Benzer şekilde özelliklerin hangi tablo alanlarına denk geldiği veya sınıfın hangi şemadaki hangi tabloya denk geldiği bilgileride niteliklerimizden alınmalıdır. Bu ihtiyaçlar doğrultusunda geliştirilen kodlar göz önüne alındığında herhangibir tipin çalışma zamanında uygulanan niteliğini elde etmek amacıyla GetCustomAttributes isimli metod kullanılmaktadır. Bu metod ilk parametre olarak elde edilmek istenen niteliğin tipini alır. Metodun belkide en önemli özelliği geriye object tipinden bir dizi döndürüyor oluşudur. Dönen bu dizi içerisinden niteliğe ait özellikleri çekebilmek için bir dönüştürme (cast) işlemi yapılmalıdır. GetCustomAttributes metodunun geriye dizi döndürmesinin sebebi, bir tipe veya üyeye birden fazla niteliğin uygulanabilecek olmasıdır. Elde edilen dizi tekrardan uygun nitelik(Attribute) tipine dönüştürüldüğünde indisleme operatörü sayesinde okunmak istenen özelliklere erişilebilir. Ki örneğimizde 0 indisli referanslar çekilmiştir. Insert metodunda kullandığımız aşağıdaki kod parçasında UrunEntity sınıfına uygulanan TabloAttribute' tipinin referansı yakalanmaktadır.

TabloAttribute tblAtr = ((TabloAttribute[])tip.GetCustomAttributes(typeof(TabloAttribute), false))[0];

Burada kullanılan GetCustomAttributes metodu tipe aittir. Sınıf içerisindeki üyelere uygulanan nitelikleri elde etmek içinde aynı yol kullanılır. Nitekim, nitelik(attribute) uygulanabilen tüm üyelerin GetCustomAttributes metodu bulunmaktadır. Söz gelimi, UrunEntity sınıfındaki özelliklere(properties) uygulanan nitelikleri çalışma zamanında elde edebilmek için GetProperties metodu ile gezilen PropertyInfo referanslarına GetCustomAttributes metodu aşağıdaki gibi uygulanmıştır.

AlanAttribute atr=((AlanAttribute[])prp.GetCustomAttributes(typeof(AlanAttribute), false))[0];

Bu şekilde nitelik referansları elde edildikten sonra söz konusu niteliğin üyelerinin değerlerine bakılabilir.

NOT : Her ne kadar makalemizin konusu nitelikleri yazmak olsada reflection ile ilgili bazı noktalara da değinmek gerekir. Örneğin Insert metodu içerisinde çalışma zamanında o anki UrunEntity nesne örneğinin özelliklerinin değerlerinin elde edilmesi için, GetValue isimli metod kullanılmıştır. Bu metodun ilk parametresi, değerleri taşıyan nesne örneğinin referansıdır.

Örneğimizi herhangibir program içerisinde test etmek için aşağıdaki gibi bir kod parçasından faydalanabiliriz. Bu amaçla örnek bir Console uygulamasını test programı amacıyla kullanabiliriz. Tek yapmamız gereken bir UrunEntity nesne örneği oluşturmak sonrasında ilgili özelliklerinde bazı değerler atamak ve Insert metodunu çağırmaktır.

UrunEntity urn = new UrunEntity();
urn.UrunAdi = "Pentium CPU";
urn.Fiyat = 90;
urn.SonSatisTarihi = DateTime.Now.AddDays(30);
urn.Insert();

Amacımız nitelikleri kavramak olduğu için, Insert metodunun içerisinde insert sorgusunu çalıştırmak için gereken Data Access Layer çağrıları yazılmamıştır. Ancak sonuçları görmek adına çalışma zamanında Insert metodunun çağırıldığı satıra bir breakpoint koyarak adım adım (step into) ilerlemekte fayda vardır. Bunun sonucunda aşağıdaki ekran görüntüsünde olduğu gibi Insert sorgusunun doğru bir şekilde oluşturulduğunu görebiliriz. Dikkat ederseniz UrunId özelliği hiç bir şekilde hesaba katılmamıştır. Ayrıca özelliklerin tabloda karşılık olan adlarına bakılarak bu sorgu cümlesi oluşturulmuştur.

Program kodumuzdan üretilen assembly içerisine ildasm.exe aracı yardımıyla bakmakta fayda vardır. Bunu yaptığımızda yazdığımız niteliklerin o anki bilgileri ile birlikte assembly' ın metadata' sına eklendiğini görebiliriz. (Ildasm aracında metadata' yı görebilmek için Ctrl+M tuş kombinasyonunu kullanırız.) Örnek olark UrunId isimli özelliğimiz için eklenen nitelik metadata içerisinde aşağıdaki şekilde görünecektir.

AlanAttribute' unun Metadata izi;

Property #1 (17000006)
-------------------------------------------------------
Prop.Name : UrunId (17000006)
Flags : [none] (00000000)
CallCnvntn: [PROPERTY]
hasThis
ReturnType: I4
No arguments.
DefltValue:
Setter : (06000015) set_UrunId
Getter : (06000014) get_UrunId
0 Others
CustomAttribute #1 (0c000011)
    -------------------------------------------------------
    CustomAttribute Type: 06000013
    CustomAttributeName: AttributeTemelleri.AlanAttribute :: instance void .ctor()
    Length: 54
    Value : 01 00 03 00 54 0e 07 41 6c 61 6e 41 64 69 09 50 > T AlanAdi P<
                : 72 6f 64 75 63 74 49 44 54 02 08 49 64 65 6e 74 >roductIDT Ident<
                : 69 74 79 01 54 02 0e 4e 75 6c 6c 49 63 65 72 65 >ity T NullIcere<
                : 62 69 6c 69 72 00 >bilir <
    ctor args: ()

Burada açık bir şekilde niteliğin(attribute) özelliklerine atanan değerler de görülebilmektedir. Benzer şekilde UrunEntity isimli sınıfımıza uygulanan TabloAttribute niteliğinin metadata içerisine yaptığı katkıyıda görebiliriz.

TabloAttribute' unun Metadata izi;

CustomAttribute #1 (0c000010)
-------------------------------------------------------
CustomAttribute Type: 06000009
CustomAttributeName: AttributeTemelleri.TabloAttribute :: instance void .ctor()
Length: 46
Value : 01 00 02 00 54 0e 09 53 63 68 65 6d 61 41 64 69 > T SchemaAdi<
            : 0a 50 72 6f 64 75 63 74 69 6f 6e 54 0e 08 54 61 > ProductionT Ta<
            : 62 6c 6f 41 64 69 07 50 72 6f 64 75 63 74 >bloAdi Product <
    ctor args: ()

Yine gördüğünüz gibi SchemaAdi özelliğine atanan Production değeri ile TabloAdi' na atanan Product değerleri buraya Value olarak alınmıştır.

Program kodlarımızı daha da geliştirmek siz değerli okurlarımızın elindedir. Örneğin, Insert, Update, Delete ve Load metodların geliştirebilir bunları gerekirse bir base sınıf içerisinde toplanabilir. Böylece geldik bir makalemizin daha sonuna. Bu makalemizde, nitelikleri(attribute) daha yakından tanımaya çalıştık ve kendi niteliklerimizimi (Custom Attributes) nasıl yazabileceğimizi incelemeye çalıştık. Gördüğünüz gibi niteliklerde birer sınıf olarak düşünüldüklerinde geliştirilmeleri son derece kolay tiplerdir. Ne varki niteliklerin asıl gücü çalışma zamanında reflection kullanıldığında ortaya çıkmaktadır. Tekrardan hatırlatmak gerekirse, amaç çalışma zamanında tiplere ve üyelere nasıl davranılacağına dair kararların verilmesinde assembly içerisindeki ekstra metadata bilgilerinde faydalanılmasıdır. Bir sonraki makalemizde görüşmek dileğiyle hepinize mutlu günler dilerim.

AttributeKullanimi.rar (47,50 kb)

Tags:  
Categories:   C#
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (3) | Comment RSSRSS comment feed
Bookmark and Share