C# ile Nesne Yönelimli Programlama I
C# ile Nesne Yönelimli Programlama I
Bu makalede nesne yönelimli programlama tekniğine kadar kullanılan yazılım geliştirmedeki yaklaşımlara göz atacağız. Daha sonra nesne yönelimli programlamanın temel kavramları ve neden böyle bir tekniğin kullanıldığı üzerinde duracağız.
Yazılım Geliştirme ve Bu Alandaki Yaklaşımlar
Kimilerine göre geçtiğimiz yüzyılın en önemli buluşu olarak kabul edilen bilgisayar teknolojisi, baş döndürücü bir hızla gelişmektedir. Bilişim sektöründeki değişimler bazen varolan teknolojilere yenilerinin eklenmesi şeklinde olabilir. Diğer taraftan bir kısım yenilikler vardır ki bu alanda büyük değişimlere ve evrimlere yolaçar. Mesela; kişisel bilgisayarların kullanılmaya başlanması veya internetin belli başlı akademik kurumların ve askeri organizasyonların tekelinden alınıp tüm insanlığın hizmetine sunulması gibi.
Hepimizin bildiği gibi bir bilgisayar sistemi iki ana parçadan oluşur. Bunlar donanım(hardware) ve yazılım(software). Donanımın yazılım ile uyumlu çalişması sonucunda sistemlerimiz sorunsuz bir şekilde bizlere hizmet verirler. Ayrıca donanımın amacımimza uygun hizmet vermesi uygun yazılımın geliştirilip kullanılmasına baglıdır.
Yazılım sektöründe program geliştirme konusunda günümüze kadar bir çok yaklaşim denenmiştir. Bunların ilki programın baştan aşağıya sırası ile yazılıp çalıştırılmasıdır. Bu yaklaşımla BASIC dili kullanılarak bir çok program yazıldığını biliyoruz. Burda sorun programın akışı sırasında değişik kısımlara goto deyimi ile atlanmasıdır. Program kodu bir kaç bin satır olunca, kodu okumak ve yönetmek gerçekten çok büyük sorun oluyordu.
ıkinci yaklaşım ise prosedürel yaklaşımdır. Programlarda bir çok işin tekrar tekrar farklı değerleri kullanılarak yapıldığı farkedildi. Mesela herhangi bir programda iki tarih arasında ne kadar gün olduğunu bulmak birçok kez gerek olabilir. Bu durumda başlangıç ve bitiş tarihlerini alıp aradaki gün sayısını veren bir fonksiyon yazılabilir ve bu fonksiyon ihtiyaç duyulduğu yerde uygun parametrelerle çağrılıp istenen sonuç elde edilebilir. Prosedürel yaklaşım Pascal ve C dillerinde uzun yıllar başari ile kullanılmıştır.
Ama her geçen gün programların daha karmaşık bir hal alması, program kodunun kurumsal uygulama projelerinde onbinlerce satırı bulması ve yazılım geliştirme maliyetinin çok arttiğını gören bilim adamları, programcılara yeni bir yaklaşımın kullanılabilineceğini öğrettiler. Bu yakaşımın ismi Nesne Yönelimli Programlama(Object Oriented Programlama)dır.
Nesne yönelimli programlama tekniği, diger yaklaşımlara nazaran, yazılım geliştiren insanlara büyük avantajlar sağlamaktadır. Birincisi karmaşık yazılım projelerinin üretilmesini ve bakımını kolaylaştırıyor olmasıdır. Diğeri ise program kodunun tekrar kullanılabilmesine (code-reusability) olanak sağlamasıdır. Bu noktada program kodunun tekrar kullanılabilmesi profesyonel yazılım şirketlerinin maliyetlerini azaltmıştır. Dolayısi ile programların lisans ücretleri düşmüş ve sektörün sürekli olarak canlı kalmasına ve rekabet içinde gelişmesine yardımcı olmuştur.
Nesne Yönelimli Programlama Nedir?
Nesne yönelimli programlamada esas olan, gerçek hayatta varolan olguların programlamaya aktarılmasındaki yeni yaklaşımdır. Prosedürel programlamada verilerimiz ve fonksiyonlarımız vardı. Yani veri ve bu veriyi işleyen metodlar etrafinda dönüyordu herşey.
Aslında nesne yönelimli programlamada da iki önemli birim veri ve veriyi işleyip mantıklı sonuçlar üreten metodlar bulunur. Ama burdaki fark gerçek hayattaki olguların da daha iyi gözlenip programlama dünyasına aktarılmasındadır.
Mesela elimizde bir ütümüz olsun. Ütünün markası, modeli, rengi, çalıstığı elektrik voltajı, ne tür kumaşları ütüleyebildiği bu ütüye ait özelliklerdir (veri). Aynı zamanda ütümüzü ısıtabiliriz, ütüleme işinde kullanabiliriz ve soğumaya bırakabiliriz. Bunlar ise ütünün fonksiyonalarıdır(metod). Eğer ütü ile ilgili bir program yapmış olsak ve nesne yönelimli programlama tekniğini kullansak hemen bir ütü sınıfı(class) oluştururduk. Bu sınıfta ütüye ait bilgiler (veriler) ve ütü ile yapabileceğimiz işler(metod) bulunurdu. O zaman nesne yönelimli programlama da bir sınıfta, sınıfa ait veriler ve bu verileri işleyip bir takiı faydalı sonuçlar üreten fonksiyonlar/metodlar bulunur.
Dahası, biz birtane ütü sinifi tasarlarsak bu sınıftan istediğimiz sayıda değişik ütüler(object veya instance) yapabiliriz. Ağagidaki şekilde ütü sınıfı ve bu sınıftan oluşturduğumuz neslerin görsel olarak anlatımı bulunmaktadır.
Sınıf Nedir ve Nasıl Tasarlanır?
Az önce de değindiğimiz gibi, sınıf bir yazılım kurgusudur ve gerçek hayattaki herhangi bir olguyu modelleyen ve bu olguya ait özellikleri(veri) ve davranışları(metdodlar) tarifleyen yapıdır.
ısterseniz ütü ile ilgili bir örnek üzerinde çalışalım. Aşağıdaki örnek C# program kodunu lütfen dikkatlice inceleyeniz.
| using System;
class Ütü_Örnegi static void Main(string[] args) Console.WriteLine(ütü1.Isın(70)); } class Ütü public Ütü(string renk, string marka) sıcaklık=15; Console.WriteLine(sıcaklık+ “derece sıcaklığında,\n ” } public string Isın(int derece) |
Yukarıdaki örnek programımızda önce altta bulunan Ütü sınıfımızı inceleyelim. Bu sınıfın sıcaklık, renk ve marka olmak üzere üç adet verisi vardır. Ayrıca Ütü sınıfımızın Ütü(string renk, string marka) şeklinde tanımlanmış yapılandırıcısı (constructor) vardır. Yapılandırıcılar bir sınıftan oluşturulan nesnelerin ilk değerlerini atama ve baslangıç işlemlerini yapmak için kullanılırlar. Ütü sınıfınımızın yapılandırcısı, oluşturulan her ütü nesnesinin sıcaklığını varsayılan değer olarak 15 dereceye ayarlıyor. Ayrıca paratmetre olarak alınan renk ve marka değerlerini de atayıp, ütüye ait özellikleri ekrana yazdırıyor.
Ütü sınıfına ait olan diğer metod ise Isın(int derece) olarak tanımladığımız metoddur. Bu metod ütünün sıcaklığını derece parametresinde verilen değer kadar artırıp sonucu string tipinde geri dönderiyor.
Ütü_Örnegi sınıfına geri dönersek, burda sadece Main() metodunun bulunduğunu görürüz. Main() metodu, C# dilinde her programda bulunması gereken zorundu bir metoddur. Çünkü programın çaliştırılması buradan başlar. Ayrıca her C# programında sadece bir tane Main() metodu bulunur. Örneğimizde Main() metodundaki en önemli satir:
Ütü ütü1= new Ütü(“Beyaz”, “AyBakır”);
satırıdır. Bu satırda Ütü sınıfına ait ütü1 nesnemizi oluşturuyoruz. Yukarıdaki satırdan da görebileceğimiz gibi herhangi bir sınıfa ait yeni nesneyi oluştururken genel olarak şu yapş kullanılır:
SınıfAdı nesneAdı = new SınıfAdı(parametre1, parametre2, … parameter N)
Referans Tipleri ve Nesneler
Nesnelerin en önemli özelliği referans tipinde olmalarıdır. Referans tiplerinde bir nesnenin değeri saklanmaz. Sadece nesnenin hafızadaki (heap memory) yeri saklanır. Bir nesnenin hafizadaki yerini new operatörü geri dönderir. Aşağıdaki program kodu ile referans tiplerine ait bir kaç temel özelliği inceleyelim:
| using System;
class Ütü_Örnegi static void Main(string[] args) Console.WriteLine(“ütü1’nin ilk özellikleri: ” + // ütü2 nesnemizi renksiz ve markasız olarak olusturalım: //ütü2 nesnemizin özelliklerine bir göz atalım: ütü2= ütü1; // ütü2 nesnemizi ütü1 nesnemize atıyoruz. Console.ReadLine(); } class Ütü public Ütü(string renk, string marka) public string Isın(int derece) public string Özellikler() |
ısterseniz kodumuzu incelemeye yine Ütü sınıfından başlayalım. Bir önceki örneğe göre sınıfımızda degişikler yaptık. Ütü sınıfının özelliklerini gösteren ayrı bir metod yazdık. Bu metod Özellikler() isimli olsun. Doğal olarak Ütü sınıfının yapılandırıcısında, sınıf oluşturulduktan hemen sonra nesnenin özelliklerini gösteren kısım kaldırıldı.
şimdi ise Ütü_Örnegi sınıfındaki Main() metodu üzerinde yoğunlaşalım. Yine, birinci örnekte olduğu gibi, ütü1 nesnemizi
Ütü ütü1= new Ütü(“Beyaz”, “AyBakır”);
ile oluşturuyoruz. Sonra ütü1 nesnesinin özelliklerini ekrana yazdırıyoruz. Bir sonraki satırda ise ütü1 nesnemizi 50 derece ısıtıp sonucu ekrana yazıyoruz.
Artık ikinci bir ütü nesnesinin oluşturmanin zamanı geldiğini düşünüp
Ütü ütü2 = new Ütü(“”,””);
satırında ütü2 nesnemizi oluşturuyoruz. Burada dikkatinizi çektiği gibi ütü2 nesnesinin renk ve marka özelliklerini boş olarak (null) oluşturuyoruz. ütü2 nesnesini oluşturunca özelliklerine gözatmak için
Console.WriteLine(“ütü2’nin ilk özellikleri: ” +
ütü2.Özellikler());
satırlarını yazıyoruz. Bir sonaki satır programımızın en önemli satırıdır. Bu satırda ütü2= ütü1 ifadesi ile ütü2 nesnesinin referansını ütü1 nesnesinin referansına atıyoruz. Değer tiplerinde “=” operatörü ile bir değişkenin değeri diğerine kopyalanır. Ama nesneler referans tipleri oldukları için, “=” operatörü bir nesnenin referansı diğerine atar. Bu durumda ütü2 nesnesi artik ütü1’in elemanlarının adreslerini içerir. Bu andan sonra ütü2’nin eskiden gösterdiği sicaklik, renk ve marka verilerine erişmemiz mümkün değildir. Artık onlar hafızada bir yerde ve biz bilmiyoruz. Bu verilerin hafızada kapladıkları alanı .NET Framework’nun çöp toplayıcısı (garbage collector) uygun gördüğü bir anda temizleyecektir.
ütü2 nesnesinin son durumda özelliklerine bakacak olursak gerçektende ütü1’i referans ettiğini görürüz. Yukarıdaki programımızı çalıştırdığımızda aşağıdaki sonucu elde ederiz:
Son olarak şu ilginç soruya cevap vermeye çalışalım. “Bir önceki program kodunun sonuna aşağıdaki kısmı eklersek ne gibi bir değişiklik olur?”
// ütü2 nesnemizin özelliklerini degiştiriyoruz:
ütü2.sıcaklık=30;
ütü2.renk=”Kırmızı”;
ütü2.marka=”Dolu”;
//ütü1 nesnemizin özellikerine bir göz atalım:
Console.WriteLine(“ütü1’nin son durumdaki özellikleri: ” +
ütü1.Özellikler());
//ütü1 nesnemizin özellikerine bir göz atalım:
Console.WriteLine(“ütü2’nin son durumdaki özellikleri: ” +
ütü2.Özellikler());
Yukarıdaki kodun eklenmiş halini derleyip çalıştirmayı deneyelim. Sonuç aşağıdaki gibi olacaktır:
Evet ütü1 ve ütü2’nin son durumdaki özellikleri aynıdır. Bu durumdan şu sonuca ulaşabiliriz: ” ütü1 ve ütü2’nin herhangi biri ile özellikleri degiştirirsek diğeri de aynı özelliklere sahip olur. Çünkü her iki nesne de hafızada aynı yere referans ediyorlar.”
Bu makalede nesne yönelimli programlamaya giriş yaptık. Nesne yönelimli programlamanın iki temel kavramı olan sınıf(class) ve nesne(object)’yi inceledik. Sonraki makalelerde nesne yönelimli programlamanın diğer önemli özellikerini birlikte incelemek dileğiyle.
C#’ta Gösterici(Pointer) Kullanmak – III
C#’ta göstericilerin kullanımı ile ilgili yazı dizisinin son bölümü olan bu yazıda gösericiler ile dizi islemlerinin nasıl yapıldıgi, stackalloc ıle dinamik bellek tahsısatının yapılmasını ve son olarak yapı(struct) göstericilerinin kullanılışını inceleyeceğiz.
Göstericiler ile Dizi İşlemleri
C#’ta tanımladıgımız diziler System.Array sınıfi türündendir. Yani bütün diziler managed type kapsamına girerler. Bu yüzden tanımladıgımız bir dizinin herhangi bir elemanının adresini fixed bloğu kullanmadan bir göstericiye atayamayız. Bu yüzden göstericiler ile dizi islemleri yaparken ya fixed bloklari kullanıp gereksiz nesne toplayıcısını uyarmalıyız yada stackalloc anahtar sözcüğünü kullanarak kendimiz unmanaged type(yönetilemeyen tip) dizileri olusturmalıyız. Her iki durumuda birazdan inceleyeceğiz.
Bildiğiniz gibi dizi elemanları bellekte ardışıl bulunur. O halde bir dizinin elemanlarını elde etmek için dizinin ilk elemanının adresini ve dizinin boyutunu bilmemiz yeterlidir. System.Array sınıfı dizilerinin elemanlarina göstericiler yardimiyla rahatlıkla ulasabiliriz. Bunun için ilk olarak fixed ile isaretlenmis bir blok içerisinde dizinin ilk elemanının adresini bir göstericiye atamalıyız. Ardından göstericinin degerini bir döngü içerisinde birer birer artırdıgımızda her döngüde dizinin bir sonraki elemanina ulaşmış oluruz. Dizilere bu sekilde erisebilmemizi sağlayan ise gösterici aritmetiğidir. Tabi dizilerin elemanlarının bellekte ardışıl bulunması da bu işlemi bu şekilde yapmamızı sağlayan ilk etkendir.
Şimdi yönetilen türden(managed) bir dizinin elemanlarına nasıl eriştiğimizi bir örnek üzerinde inceleyelim.
| using System;
class Gosterici int[] a = {1,2,3,4}; fixed(int* ptr = &a[0]) |
Programı derlediğinizde dizinin elemanlarının
1
2
3
4
seklinde ekrana yazdırıldığını görürsünüz. (programı derlerken /unsafe argümanını kullanmayı unutmayın). Programın kritik noktası
| fixed(int* ptr = &a[0]) |
satırı ile dizinin ilk elemanının adresinin elde edilmesidir. Zira dizinin diğer elemanlarıda sırayla ilk elemandan itibaren her eleman için adres değeri 4 byte artacak şeklindedir. Diger önemli nokta ise for döngüsü içindeki
| *(ptr+i) |
ifadesidir. Bu ifade her döngüde ptr göstericisinin adres bileşeni, döngü degişkeni kadar artırılıyor, sözgelimi döngü degişkeni 1 ise ptr’nin adres bileşeni 4 artırılıyor. Bu da dizinin ikinci elemanının bellekte bulunduğu adrestir. İçerik operatörü ile bu adrese erişildiğinde ise dizinin elemanı elde edilmis olur.
Gösterici dizileri ile ilgili diğer önemli nokta göstericilerin indeksleyici gibi kullanılabilmesidir. Örneğin yukarıdaki örnekte bulunan
| *(ptr+i) |
ifadesini
| ptr[i] |
şeklinde degiştirebiliriz. Burdan aşağıdaki eşitlikleri çıkarabiliriz.
| *(ptr+0) == ptr[0] *(ptr+1) == ptr[1] *(ptr+2) == ptr[2] *(ptr+3) == ptr[3] |
Dikkat: ptr[i] bir nesne belirtirken (ptr+i) bir adres belirtir.
Dizilerin isimleri aslinda dizilerin ilk elemanının adresini temsil etmektedir. Örneğin aşagıdaki programda bir dizinin ilk elemanının adresi ile dizinin ismi int türden bir göstericiye atanıyor. Bu iki göstericinin adres bileşenleri yazdırıldıgında sonucun aynı olduğu görülmektedir.
| using System;
class Gosterici fixed(int* ptr1 = a, ptr2 = &a[0]) |
Programı derleyip çalıştırdığınızda ekrana alt alta iki tane aynı sayının yazıldığını görürsünüz.
Yönetilen(managed) tiplerle çalışmak her ne kadar kolay olsa da bazı performans eksiklikleri vardır. Örneğin bir System.Array dizisinin bir elemanına erişmek ile stack bölgesinde olusturacagımız bir dizinin elemanına ulaşmamız arasında zaman açısından büyük bir fark vardır. Bu yüzden yüksek performanslı dizilerle çalışmak için System.Array sınıfının dışında stack tabanlı diziler oluşturmamız gerekir. Stack tabanlı diziler yönetilemeyen dizilerdir. Bu yüzden bu tür dizileri kullanırken dikkatli olmalıyız. Çünkü her an bize tahsis edilmeyen bir bellek alanı üzerinde islem yapiyor olabiliriz. Ancak yönetilen dizilerde dizinin sınırlarını aşmak mümkün degildir. Hatırlarsanız bir dizinin sınırları aşılınca çalışma zamanında IndexOutOfRangeException istisnai durumu meydana geliyordu. Oysa stack tabanlı dizilerde dizinin sınırları belirli degildir ve tabiki dizinin sınırlarını aşmak kısıtlanmamıştır. Eğer dizinin sınırları aşılmışsa muhtemelen bu işlem bir hata sonucu yapılmıştır. Hiçbir programcı kendisine ait olmayan bir bellek alanında islem yapmamalıdır. Aksi halde sonuçlarına katlanması gerekir.
Stack tabanlı diziler stackalloc anahtar sözcüğü ile yapılır. stackalloc bize istediğimiz miktarda stack bellek bölgesinden alan tahsis eder. Ve tahsis edilen bu alanın başlangıç adresini geri döndürür. Dolayısıyla elimizde olan bu baslangıç adresi ile stackalloc ile bize ayrılmış olan bütün bellek bölgelerine erişebiliriz. stackalloc anahtar sözcüğünün kullanımı aşağıdaki gibidir.
| int * dizi = stackalloc int[10]; |
Bu deyim ile stack bellek bölgesinde 10*sizeof(int) = 40 byte’lık bir alan programcının kullanmasi için tahsis edilir. Bu alan, dizinin faaliyet alanı bitinceye kadar bizim emrimizdedir. Tahsis edilen bu 40 byte büyüklüğündeki bellek alanının ilk byte’ının adresi ise int türden gösterici olan dizi elemanına aktarılır. Dolayısyla dizi göstericisi ile içerik operatörünü kullandığımızda bize ayrılan 10 int’lik alanın ilk elemanına erişmiş oluruz.
! stackalloc ile tahsis edilen bellek alanlarının ardışıl olması garanti altına alınmıştır.
stackalloc ile alan tahsisatı yapılır. Ancak alan tahsisatı yapılan bellek bölgesi ile ilgili hiçbir islem yapılmaz. Yani yukaridaki deyim ile, içinde tamamen rastgele değerlerin bulundugu 40 byte’lık bir alanımız olur. Bu alandaki değerlerin rastgele degerler olduğunu görmek için asagidaki programı yazın.
| using System;
class Gosterici for(int i=0; i<10;++i) |
! Yukarıdaki programda bir gösterici ile bellekteki ardışıl bölgelere indeksleyici operatörü ile nasıl eriştiğimize dikkat edin.
Programı /unsafe argümani ile beraber derleyip çalıştırdıktan sonra aşağıdaki ekran görüntüsünü elde etmeniz gerekir. Tabi bu değerler rastgele oldugu için sizdeki görüntü tamamen farklı olacaktır. Rastgele değerden kasıt çalışma zamanında Random gibi bir sınıfin kullanılıp rastgele bir sayı üretilmesi değildir. Burdaki sayılar daha önce çalışmiş olan programlardan kalan çöp değerlerdir.
| C:\Programlar\StackAlloc *(dizi + 0) = 0 *(dizi + 1) = 1244236 *(dizi + 2) = 1243328 *(dizi + 3) = 1350496 *(dizi + 4) = 124334 *(dizi + 5) = 1287174 *(dizi + 6) = 1243328 *(dizi + 7) = 0 *(dizi + 8) = 0 *(dizi + 9) = 1243404 C:\Programlar\StackAlloc |
| Console.WriteLine(“*(dizi+{0}) = {1}”,i,dizi[i]); |
satırındaki
| dizi[i] |
yerine
| *(dizi + i) |
yazmamız herhangi birşeyi değiştirmezdi. Daha önce bu iki kullanımın eşdeğer olduğunu belirtmiştik.
Stack tabanli dizilerle ilgili bilinmesi gereken en önemli nokta dizinin sınırlarının aşılması ile ilgilidir. Daha önceden de denildiği gibi stack tabanli dizilerin sınırları aşıldığında herhangi bir uyarı verilmez. Elbetteki program başarıyla derlenir ancak çalımma zamanında bize ait olmayan bir adresin içeriğini degiştirmiş oluruz ki bu da bir programcının başina gelebilecek en tehlikeli durumdur. Örneğin aşağidaki programda stackalloc ile 10 int türünden nesnelik alan tahsis edilmektedir. Buna rağmen istediğimiz kadar alanı kullanabiliyoruz.
| using System;
class Gosterici for(int i=0; i<50;++i) |
stackalloc ile oluşturacağımız dizilerin boyutu derleme zamanında bilinmek zorunda değildir. Örneğin çalışma zamanında kullanıcının belirlediği sayıda elemana sahip olan bir ardışıl bellek bölgesi aşagıdaki programda oldugu gibi tahsis edilebilir.
| using System;
class Gosterici try int * dizi = stackalloc int[(int)10]; for(int i=0; i<(int)boyut; ++i) |
Bu program ile çalışma zamanında 7 elemanlı stack tabanlı bir dizinin oluşturulduğunu aşağıdaki ekran görüntüsünden görebilirsiniz.
Yapı(Struct) Türünden Göstericiler
int,char,double gibi veri türleri aslında birer yapıdır. Bu konun başında bütün değer tipleri ile gösterici tanımlayabileceğimizi söylemiştik. C# temel veri türlerinin yanısıra kendi bildirdiğimiz yapılar da değer türündendir. O halde bir yapı göstericisi tanımlayabilmemiz doğal bir durumdur. Yapı göstericilerinin tanımlanması temel veri türlerinden gösterici tanımlama ile aynıdır. Ordaki kuralların tamamı yapılar içinde geçerlidir. Fakat yapı göstericisi tanımlamanın bir şartı vardır, oda yapının üye elemanlarının tamamının değer tipi olma zorunluluğudur. Örneğin aşağıdaki yapı göstericisi tanımlaması geçersizdir.
| using System;
struct Yapi public Yapi(int x,char c, string str) class StackAlloc Yapi* pYapi = &yapi; |
Yukarıda ‘Yapi’ türünden göstericinin tanımlanamamasının sebebi yapının yönetilen türden(managed type) bir üye elemanının bulunmasıdır. Bu üye elemanı da doğal olaral string türüdür. Yapı bildiriminden string türünü çıkarıp aşağıdaki gibi ilgili değişiklikleri yaptığımızda ‘Yapi’ türünden göstericileri tanımlayabiliriz.
| using System;
struct Yapi public Yapi(int x,char c) class StackAlloc Yapi* pYapi = &yapi; |
Yapı göstericileri üzerinden yapı göstericisinin adresine ilişkin nesnelerin elemanlarına özel bir operatör olan -> operatörü ile erişebiliriz. Örneğin yukarıdaki programın Main() metodunu asağıdaki gibi değiştirdiğinizde ekrana yapı nesnesinin x ve c elemanları yazdırılacaktır. Tabi Yapi’nın üye alamanlarını public olarak değiştirmeniz gerekecektir. Çünkü ok operatörü ilede olsa ancak public olan elemanlara ulaşabiliriz.
| unsafe static void Main() { Yapi yapi = new Yapi(2,’a’); Yapi* pYapi = &yapi; Console.WriteLine(“yapi.x= ” + pYapi->x); |
Not: ‘ -> ‘ operatörüne ok operatörü de denilmektedir.
Yapının public olan elemanlarına ok operatörü yerine yapı nesnesinin içeriğini * operatörü ile elde edip nokta operatörü ile de ulaşabiliriz. Buna göre yukarıdaki Main() metodu ile asagidaki Main() metodu eşdeğerdir.
| unsafe static void Main() { Yapi yapi = new Yapi(2,’a’); Yapi* pYapi = &yapi; Console.WriteLine(“yapi.x= ” + (*pYapi).x); |
Her iki Main() metodunun ürettiği çıktı aynıdır.
Göstericilerin en çok kullanıldığı diğer bir uygulama alanı da karakter işlemleridir. Bir yazıyı karekter dizisi olarak temsil edip yazılar ile ilgili işlemler yapılabilir. C#’ taki string türünün altında geçekleşen olaylarda zaten bundan ibarettir. Karakter dizileri ile ilgili en önemli nokta bir yazıyı char türden bir göstericiye atayabilmemizdir. Karekter dizileri olan stringlerdeki her bir karakter bellekte ardışıl bulunmaktadır. Dolayısıyla yazıdaki ilk karakterin adresini bildiğimizde yazıdaki bütün karakterlere erişebiliriz. C#’taki string türü yönetilen tip(managed type) oldugu için char türden bir göstericiye bir yazının ilk karekterinin adresini atamak için fixed anahtar sözcüğünü kullanmalıyız. Aşağıdaki programda bir yazının char türden göstericiye nasıl atandığını ve bu gösterici ile yazıdaki her karaktere ne şekilde erişildiğini görüyorsunuz.
| using System;
class KarakterDizisi |
Buradaki en önemli nokta ptr[i]’ nin ” karakteri ile karşılaştırıldığı yerdir. Göstericiler ile bellekte char türünün büyüklügü kadar ilerlerken yazının nerede sonlandıgını bilemeyiz. Bunun için
char* ptr = “Sefer Algan”
deyimi ile belleğe yerlestirilen ‘n’ karakterinden sonra null değerini ifade eden ” karekter yerleştirilir. Bu karektere rastlanıldığı zaman yazının sonuna gelmiş bulunuyoruz.
Not: Göstericilerle ilgili yayınlanan bu makale dizisi yazmış olduğum ve yakında Pusula yayıncılıktan çıkacak olan C# kitabından alınmıştır.
.NET’te Dinamik Kontrol Oluşturma
Bu yazıda windows ve web uygulamalarında dinamik kontrollerin nasıl oluşturulacağını ve oluşturulan dinamik kontrollere erişim yöntemlerini inceleyceğiz. Ayrıca bu yazıda dinamik kontrol oluşturma ile ilgili kapsamlı bir örnek kod yeralmaktadır.
Bir çok uygulamada(özellikle oyunlarda) kontrollerimizin sayısını tasarım aşamasında bilemeyebiliriz. Örneğin hepimizin bildiği mayın tarlası oyununda, oyun alanı bir çok kareden oluşmaktadır. Üstelik oyun alanı, kullanıcının isteğine göre değişebilmektedir. Eğer tasarım aşamasında bütün kontroller form üzerine yerleştirilşmiş olsaydı hem bu kontrollerin yönetimi zorlaşacaktı hemde gereksiz yere bir çok kod yazılmış olacaktı. Buna benzer bir durum web formları ile programlama yaparken de görülebilir. Örneğin bir anket sisteminde anketin seçenekleri kadar CheckBox kontrolünü dinamik olarak web formunun üzerine yerleştirebilmemiz gerekir. Eğer bu imkanımız olmasaydı Web formları çok esnek bir programlama modeli sunmazdı. Neyse ki Microsoft tasarımcıları herşeyi düşünmüş… 🙂
Dinamik kontrol oluşturmaya geçmeden önce nasıl bir form tasarımı yapmaya çalıştığımıza bakalım. Aşağıdaki formdan da gördüğünüz üzere 8X8’lik bir dama tahtasının her bir karesi aslında bir Button kontrolünden ibarettir. Tasarım aşamasında 64 tane Button kontrolünü form üzerine yerleştirip herbirni tek tek düzenlemek yerine bir döngü vasıtası ile istediğimiz sayıda Button kontrolünü oluşturup Form elemanına ekleyeceğiz.
ToolBox penceresinde görüdüğünüz bütün kontrolleri temsil eden sınıflar System.Windows.Forms isim alanında bulunan Control isimli sınıftan türetilmiştir. Bazı kontroller ise yapısında değişik kontrolleri barındıracak şekilde tasarlanmıştır. Örneğin Form penceresi üzerene eklenen kontroller bu duruma bir örnektir. Kontrolleri gruplamak için kullanılan GrouopBox ta bu şekildedir. Bu özel kontrollere yeni bir kontrol eklemek için bu Control türünden olan Controls isimli koleksiyon kullanılmaktadır. Örneğin bir form üzerine yeni bir Button kontrolü eklemek için aşağıdaki deyimleri yazmalıyız.
| Button tb = new Button(); Form1.Controls.Add(button) |
Yukarıdaki deyimleri bir döngü içinde yapıp oluşturduğumuz Button kontrollerinin ilgili özelliklerini değiştirdikten sonra dama tahtasını rahatlıkla oluşturabiliriz.
Şimdi bu yazının en altında bulunan linki kullanarak projeyi bilgisayarınıza yükleyerek projeyi açın. Form1 sınıfının içinde bulunan aşağıdaki TahatCiz() isimli metodu inceleyerek kontrollerin dinamik olarak nasıl yaratıldıklarını inceleyin.
| private void TahtaCiz(int Boyut1,int Boyut2, int En, int Boy) { int satir =0; int sutun =0; Button tb = new Button(); for(int i=0; i < Boyut1*Boyut2; i++) if(i % Boyut2 == 0) tb = new Button(); Point p = new System.Drawing.Point(tb.Size.Width + (sutun-1)*tb.Width,tb.Size.Height + (satir-2)*tb.Height); tb.Location = p; Butonlar.Add(tb); this.ClientSize = new Size(tb.Width*Boyut2 ,tb.Height*Boyut1); |
TahtaCiz() isimli metodu biraz inceleyelim. Bu metot kendisine gönderilen bilgiler ışığında form üzerine dinamik bir dama tahtası çizmektedir. Ayrıca işe yarar bir kaç işlem daha yapmaktadır. Bu metoda gönderilen Boyut1 ve Boyut2 değişkenleri dama tahtasının boyutlarını belirtmektedir. En ve Boy parametreleri ise tahtadaki her bir karenin enini ve boyunu belirtmektedir. Bu bilgileri parametre olarak almamız istediğimiz boyutta dama tahtasını çizebileceğimiz anlamına gelmektedir. Zaten form üzerine konulan bir menü yardımıyla dama tahtasının boyutu ve karelerin en ve boyu istenildiği gibi değiştirilmektedir. Bu metot sadece butonların form üzerine yerleştirilmesinden sorumludur. RenkAyarla() isimli diğer bir metot ise yerleştirilen bu butonların bir algoritmaya göre renklerini ayarlamayı sağlamaktadır. Bu metot içinde oluşturulan buton kontrollerine başka metotlar içinde erişebilmek için oluşturulan her kontrol global düzeyde tanımlanan Butonlar isimli bir Arraylist koleksiyonuna eklenmektedir. Bu metottaki diğer önemli bir satır ise oluşturulan dama tahtasınının boyutlarına göre form nesnesinin yeniden şekillendirilmesidir. Bu işlem
this.ClientSize = new Size(tb.Width*Boyut2 ,tb.Height*Boyut1);
satırıyla yapılmaktadır.
Not: Her bir Button kontrolünün yanyana ve alt alta nasıl yerleştirildiğini daha iyi anlamak için size tavsiyem kağıt üzerine bir dama tahtası çizip her bir karenin konumuna ilişkin matematiksel bir ifade bulun. Böylece herşey daha açık olacaktır.
Aşağıdaki satırda her bir buton kontrolünün Text özelliği döngü değişkeni olan i’ye atanmaktadır.
tb.Text = i.ToString();
Dinamik kontrolleri oluşturma ile ilgili diğer bir önemli nokta da kontrollere ilişkin olayların nasıl çağrılacağıdır. Dikkat ederseniz yukarıdaki metotta oluşturulan bütün kontrollerin Click olayına EventHandler temsilcisi yardımıyla button1_Click() metodu iliştirilmiştir. Oluşturulan butonlardan herhangi birine tıkladığınızda bu metot işletilecektir. Peki hani butonun tıklandığını nasıl anlayacağız. Bunun için button1_Click() metodunun bildirimine bakalım.
| private void button1_Click(object sender, System.EventArgs e) { Button b = (Button)sender; MessageBox.Show(b.Text); |
Yukarıdaki metodun bir parametresi olan sender değişkeni bu metodun hangi button’un tıklanması sonucu işletildiğini tutmaktadır. Bize bu imkanı sağlayan elbetteki EventHandler temsilcisidir(delegate).
Dönüşüm operatörü kullanılarak sender nesnesi Button nesnesine dönüştürülümektedir. (Bu işleme unboxing denildiğini de hatırlatmak isterim)
Bu durumda, örneğin üzerinde 39 yazan Button’a tıkladığınızda gösterilen mesaj kutusuna gibi 39 yazacaktır.
Aşağıdaki ekran görüntüsündeki dama tahtasının biçimi uygulamadaki Ayarlar menüsündeki Renk sekmesi tıklanarak oluşturulabilir.
Not: Dama tahtası biçiminde bir form oluşması için Renk Seçimi penceresindeki Gradyan Katsayısı ortalanmalıdır. Renk ayarlama işleminin nasıl yapıldığı bu yazı kapsamında olmadığı için detaylarını anlatmayacağım. RenkAyarla() isimli metodu incelemenizi tavsiye ediyorum.
Web formları ile dinamik kontrol oluşturmak yukarıda anlattıklarımdan farklı değil. Yeni bir ASP.NET uygulaması açarak aşağıdaki gibi bir form tasarlayın.
Dinamik olarak oluşturacağımız kontrolleri Page sınıfına akleyebileceğimiz gibi PlaceHolder kontrolünü de kullanabiliriz. Ben bu iş için PlaceHolder kontrolünü tercih ettim.
“Oluştur” isimli butonun Click olayına ilişkin metodunu aşağıdaki gibi değiştirip sonucu inceleyelim.
| private void Button1_Click(object sender, System.EventArgs e) { int Adet = Convert.ToInt32(TextBox1.Text); for(int i=0; i<Adet; ++i) PlaceHolder1.Controls.Add(yeni); |
Projeyi derleyip çalıştırın ve TextBox kutusuna bir tamsayı girip “Oluştur” butonuna tıklayın. İşlermleri başarı ile yaptıysanız aşağıdaki gibi “PlaceHolder” kontrolü içinde 4 adet TextBox kontrolü dinamik olarak yerleştirilecektir.
Not : Sunucu kontrolü olmayan <br> etiketinin LiteralControl olarak tanımlandığına dikkat edin.
Dinamik kontrolleri oluşturmayı öğrendiğinize göre artık web tabanlı bir mayın tarlası oyunu yapmanın zamanı geldi sanırım 🙂 Biraz çaba ile bu oyunu rahatlıkla yapabileceğinizi düşünüyorum.
