Takıldığınız bir şey mi var?

.NET, front-end development, data structures veya desing patterns gibi konularda sorular açın ve en kısa zamanda cevaplayalım...


21
Ocak

.NET'teki kadim dostumuz delegate veri türlerinin çalışma prensiplerini C/C++ dillerindeki function pointer kavramı ile anlayalım...

.NET platformunda uygulama geliştirmiş hemen herkes az ya da çok event adını verdiğimiz mekanizmayı kullanmıştır. Hepimizin bildiği gibi event, runtime esnasında client tarafından belirlenmiş bir fonksiyonun istenilen bir durumun gerçekleşmesi esnasında invoke edilmesini sağlayan dinamik bir mekanizmadır. Bu mekanizmanın geri planında System.Delegate türlü veri tiplerinin varlığı yer almaktadır. Aslında, nasıl ki bir In32, RAM belleğin bir bölgesindeki 32 bit büyüklüğünde sayıya işaret ediyor ise delegate türlü bir veri tipi de RAM bellekteki bir fonksiyon adresine işaret etmektedir. Bu yazımızın devamında event’ların çalışma prensiplerini ve akabinde delegate veri türlerinin nasıl bir yapıya sahip olduğunu anlamaya çalışacağız.


C# uygulamasında bir delegate nasıl tanımlanır veya define edilmiş bir sınıf içinde istenilen bir event nasıl hazır hale getirilir gibi konularda yazı yazmıyorum. Bu konularda zaten istemediğiniz kaynak bulabilirsiniz ki bahsettiğim konularda kendinizi eksik hissediyorsanız mutlaka eksiğinizi tamamladıktan sonra yazımın devamını okuyun.

C# programlama dili, pointer veri tiplerini doğrudan desteklemediği(gerekli olduğunda(!) birkaç ayar ile pointer tipleri C# ile kullanılabilir) için daha doğrusu C#’ın doğası ve yetenekleri gereği işaretçi türlerine ihtiyaç duymadığımız için aslında memory management, memory allocation, memory leak hatta class-instance gibi kavramlara uzak kalıyoruz. Hele ki programlama ile programlama dili kavramlarını aynı zamanneden ve bilgisinin arkasına saklanıp Data Structures gibi genel konular hakkında bir şeyler yazıp çizmemiş kimseler, ne kadar deneyimli olurlarsa olsunlar aslında bildiklerinin ezberden daha fazlası olmadığının farkında olamıyorlar. Lafı uzatmadan C/C++ programlama dillerindeki function pointer kavramından olaya girelim.


Function pointer, fonksiyon işaretçisi anlamına gelen bir kelime olup, imzası önceden belirtilmiş bir fonksiyonun runtime esnasındaki bellek adresini tutmak ile görevli bir veri türü olarak düşünülebilir.(Aslında ayrı bir veri türü de değil, sadece bir işaretçidir.)


void myFunction(int argument) {
   std::cout << argument << std::endl;
}

Gördüğünüz gibi elimde son derece basit halde duran bir C++ fonksiyonu mevcut. Void tipli olan bu fonksiyonun görevi parametre olarak aldığı sayıyı konsol ekranına yazdırmaktan daha fazlası değil.

int main()
{
       void(*funcPointer)(int);
       funcPointer = myFunction; //dikkat
       funcPointer(12);
       //12 yazılır.
       getchar();
}

İşin aslı main fonksiyonunda gizli. Ana fonksiyonun gövdesindeki ilk kod satırına bakarsanız burada belki daha önce hiç görmediğiniz bir söz dizimi ile void(*funcPointer)(int) ifadesini göreceksiniz. Bu ifade, void tipli ve int tipinde bir parametre alan function pointer tanımlamasıdır. Biraz önce baktığınız myFunction isimli fonksiyonun imzası ile birebir örtüşen bu fonksiyon işaretçisinin içerisine myFunction isimli fonksiyonun atamasını yapabilirim. Zaten C# dilindeki delegate türlerinin kullanımı esnasında da ilgili delegate ile hedef fonksiyonun örtüşmesi zorunluğunu biliyoruz. Buradan yola çıkarak funcPointer ismini verdiğim bu işaretçiye gönül rahatlığıyla tanımladığım myFunction isimli fonksiyonu atıyorum. Dikkat ettiyseniz myFunction() şeklinde atama yapmıyorum, çünkü bu işlem atama işlemi değil ilgili fonksiyonu çağırma işlemi olur. C/C++ dillerinde de tıpkı C#’ta olduğu gibi fonksiyon ismi aslında bir işaretçidir. Daha sonra işaretçimi normal bir fonksiyon çağırır gibi kullandığımda konsol ekranına 12 değerinin yazıldığını göreceğim. Aslında arka tarafta olan şey, fonksiyon işaretçisinin myFunction isimli fonksiyonun bellek adresini tutması ve bu işaretçi üzerinden yapılan işlemlerin işaret ettiği bellek adresindeki fonksiyona ulaşmasıdır.

Şimdi gelelim somut bir ihtiyaç örneğine. Düşünün ki bir sınıf kütüphanesi yazıyorsunuz ve bunu dağıtacaksınız… Kütüphanenizdeki bir sınıfın, belirlenmiş bir şartın sağlandığı durumlarda kütüphanenizi kullanan bir kimse tarafından belirlenmiş bir fonksiyonun çalışması gerekiyor. Ne yaparsınız? İlk akla gelen ihtiyaçlara uygun imzalanmış bir temsilcinin alytapısından faydalanan bir event tanımlamasını sınıf içerisinde yapmak olur. Peki, bu delegate aslında nasıl çalışıyor?

struct eventArgs
{
   int data;
};

typedef void(*eventHandler)(eventArgs);


C# programlama dilinden alışık olduğumuz bir durum, bir çok EventHandler’ın ilgili olayla alakalı verileri invoke edilen fonksiyona düşürmek için EventArgs sınıfından türemiş bir nesneden faydalanmasıdır. Mademki ben client tarafındaki fonksiyona olaya ilişkin verileri eventArgs türünde döndüreceğim o hade eventArgs isimli bir yapı tanımlıyorum ve void tipli ve eventArgs parameteresi alan bir function pointer tipi define ediyorum.

class MyClass
{
private:
       eventHandler handler;
public:
        MyClass() {}
       ~MyClass() {}

       void addHandler(eventHandler handler) {
             this->handler = handler;
       }

       void testHandler(int argument) {
             if (argument < 0) {
                    eventArgs args = eventArgs();
                    args.data = argument;
                    handler(args); //invoke et
             }
       }
};

Evet, define ettiğim sınıf içinde bir adet eventHandler yani void(*func)(eventArgs) işaretçisi tanımladım ve private durumda olan bu üyeye değer ataması yapmak adına addHandler isimli foksiyon tanımladım. Diğer bir fonksiyon olan testHandler ile event-delegate uygulamasını test etmeye çalışacağım. O halde parametre gelen sayının 0’dan küçük olması şartı sağlandığında çalıştırılmak üzere temsilcime uygun bir fonksiyon tanımlamalıyım.

void sampleFunction(eventArgs args) {
       std::cout << "Function called with" << args.data << std:: endl;
}

Fonksiyonun örneğimizin başında tanımladığımız işaretçi ile örtüştüğüne dikkat gösterin. Fonksiyonum tıpkı temsilci gibi void tipli ve eventArgs tipli bir parametre alıyor.


int main()
{
       MyClass obj = MyClass();
       obj.addHandler(sampleFunction);//dikkat
 
       /*
         veya:
                eventHandler myHandler = sampleFunction;
                 obj.addHandler(myHandler);
       */

       obj.testHandler(10);
       //bir şey yazılmaz
       obj.testHandler(-100);
       //Function called with -100 yazılır.
       getchar();
}


-100 parametresi argument < 0 şartını yerine getireceği için addHandler fonksiyonu ile sınıf içindeki işaretçiye kazandırdığımız sampleFunction isimli fonksiyon yürütülecek ve client tarafından belirlenmiş fonksiyonun çalıştırılması sağlanmış olacaktır. Örnekten gördüğümüz kadarıyla C# dilindeki delegate-event kavramları, C/C++ dillerindeki function pointer’ların .NET’çesidir diyebiliriz. .NET ortamındaki temsilciler birden fazla fonksiyona işaret edebildiklerinden dolayı daha gelişmiş bir örneği C++ dili ile yapmak isterseniz siz tanımladığınız temsilci türünde bir LinkedList yapabilir veya hazır bir koleksiyon kullanabilirsiniz. Böylelikle ilgili şartın sağlanması durumunda client tarafından belirlenmiş birden fazla fonksiyonun invoke edilmesini herhangi bir hazır işlevden yararlanmaksızın sağlamış olursunuz.

Evet, bu yazımızda C# gibi yeni nesil ve safe bir programlama dilindeki delegate-event kavramlarını C/C++ programlama dillerindeki function pointer kavramı ile oldschool bir biçimde incelemeye çalıştık. Umarım faydalı bir yazı olmuştur. Esenlikle kalın, hatasız kodlamalar dilerim.