28 Şubat 2011 Pazartesi

C# Encapsulation ve Property Kavramı

.Net'in en güzel özelliklerinden birisi, programcının yükünü minimuma indirmesidir. Bir önceki yazımda, Encapsulation kavramından bahsederken field'ı kapsülleyerek, get ve set adlı iki metot yardımı ile kontrolü sağlamıştık. .Net, bizim için bu iki metodu daha rahat kodlayabileceğimiz ve kullanabileceğimiz bir yapı hazırlamıştır. Bu yapı Property'dir (Özellik). Field'lar kapsüllenerek Property'ler yardımı ile dışarıya açılabilmektedir. Nesne Yönelimli Felsefeye göre hiçbir field public olmamalıdır. Doğru kodlama; field'ların private yapılarak, Property'ler yardımı ile dışarı açılmasıdır.

Şimdi Id, Ad ve Fiyat alanları olan bir Urun sınıfı tasarlayalım ve bu alanları kapsülleyerek Property'ler yardımı ile dışarıya açalım.

class Urun

    {

        int _id;

 

        public int Id

        {

            get { return _id; }

            set { _id = value; }

        }

 

        string _ad;

 

        public string Ad

        {

            get { return _ad; }

            set { _ad = value; }

        }

 

        double _fiyat;

 

        public double Fiyat

        {

            get { return _fiyat; }

            set { _fiyat = value; }

        }

    }

İpucu: Kapsüllenmek istenen field'ın üzerine mouse ile gelinerek, ctrl R+E kısayolu kullanılarak property'leri oluşturmak mümkündür. (ctrl tuşuna basılı olarak R'ye, daha sonra E'ye basılması yeterlidir. 3'üne de aynı anda basılmasına gerek yoktur.)

Yukarıda gördüğümüz gibi, her alan private olarak tanımlandıktan sonra, public property'ler yardımı ile kontrol altına alınmış oldu. set metodu gelen değeri field'a atarken, get metodu da field'ı return etmektedir. Aslında en önemlisi; kontrolün, nesnenin kendisini tarafından yapılmasıdır.

-Property, yazımı her ne kadar benzese de bir metot değildir, dolayısı ile () parantezlerini kullanamayız.

resim10

Field'lar ve Property'ler sınıfın 2 ayrı üyesidir. Sadece birbirlerini kullanmaktadırlar. Ayrıca şekilde görüldüğü gibi Property simgeleri, pembe renkli olan metot simgelerinden farklıdır.

Şimdi Fiyat Property'si içerisinde negatif değer girilememesi kontrolünü yapalım;

double _fiyat;

 

public double Fiyat

{

      get { return _fiyat; }

      set

      {

           if (value > 0)

               _fiyat = value;

           else

               Console.WriteLine("Fiyat negatif olamaz");

       }

 }

Şimdi de Program sınıfında bir Urun nesnesi oluşturalım ve Fiyat Property'sine değer ataması yapalım;

class Program

    {

        static void Main(string[] args)

        {

            Urun u = new Urun();

            u.Fiyat = -100;

            Console.WriteLine(u.Fiyat);

        }

    }

Alacağımız sonuç, bir önceki yazıdaki uygulamamız ile aynı sonuç olacaktır;

resim5

Son olarak ILDASM yardımı ile Programın IL kodlarını inceleyelim ve Property'lerin içerisindeki get ve set metotlarının varlığını kanıtlayalım;

resim11

IL kodlarda görüldüğü gibi, arka tarafta bizim için get ve set metotları yazılmış durumda. Bu noktada Property'ler, programcıya sağlanan bir kolaylık olarak da düşünebilirler.



Nesne Yönelimli Programlamanın 3 temel felsefesinden biridir.( Diğerleri; Inheritance(Kalıtım) ve PolyMorphism(Çok Biçimlilik) ).En yakın türkçe çevirisi "kapsüllemek"dir. Nesnenin üyelerine yapılan erişimin kontrol altına alınmasına ve bu kontrolün nesnenin kendisi tarafından yapılmasına dayanan felsefedir.. Amaç; field'ları private yaparak bu alanlara dışarıdan erişimi önlemek ve get ile set metotları aracılığıyla kontrolü sağlamaktır. Şimdi bu felsefeyi bir örnek üzerinden anlamaya çalışalım.

Elimizde Id, Ad ve Fiyat alanları olan bir Urun sınıfı olsun. Klasik programcılıktaki tasarım aşağıdaki gibidir.

class Urun

    {

        public int Id;

        public string Ad;

        public double Fiyat;

    }

Eğer alanların başına "public" erişim belirleyicisini yazmasaydık, üyeler default olarak "private" olacak ve başka bir sınıftan bu üyelere erişim yapılamayacaktı. Şimdi Program sınıfında bir Urun nesnesi oluşturalım;

resim2

Mavi semboller ile gözüken alanlar, nesnenin field'larıdır. Field'ların erişim belirleyicileri public olduğundan dolayı Program sınıfında bu nesnelere erişebildiğimizi görüyoruz.

Oluşturduğumuz Urun nesnelerinin fiyatlarının sıfırdan küçük olamaması durumunu inceleyelim. Yani kullanıcı negatif bir değer giremesin istiyoruz. Bu kontrolü Program tarafında da yapabiliriz. Ancak bu iş Urun sınıfı nesnelerini ilgilendirdiğinden dolayı, kontrolü Urun sınıfı içerisinde yapmak daha doğru bir kodlama olacaktır. Bu kontrolü yapabilmemiz için Fiyat field'ını encapsulation felsefesine göre private yaparak 2 adet metot ekleyelim, birisi nesneye değer ataması kontrol eden Set_Fiyat metodu, diğeri de atanan değeri geriye döndüren Get_Fiyat metodu olsun;

class Urun

    {

        public int Id;

        public string Ad;

        private double Fiyat;

 

        public void set_Fiyat(double value)

        {

            if (value > 0)

                Fiyat = value;

            else

                Console.WriteLine("Fiyat negatif olamaz...");

        }

 

        public double get_Fiyat()

        {

            return Fiyat;

        }

    }

Yukarıdaki kodda görüldüğü gibi, oluşturulan Urun nesnesi üzerinden Set_Fiyat(değer) metodu çağırıldığında verilen değerin uygun olması durumunda field'a atama yapılmakta, aksi takdirde kullanıcıya hata mesajı verilmektedir. Get_Fiyat() metodu da atanan bu değeri geri dönmektedir. Ayrıca, kapsüllenen field'ın isimlendirmesi, başına "_" işareti konarak daha doğru, okunur ve standartlara uygun bir kodlama sağlar. (Yine de isimlendirme tercihe bağlıdır) Şimdi Program sınıfında bir Urun nesnesi oluşturup değer ataması yapalım;

class Program

    {

        static void Main(string[] args)

        {

            Urun u = new Urun();

            u.set_Fiyat(-100);

            Console.WriteLine(u.get_Fiyat());

        }

    }

Kodu çalıştırdığımızda aşağıdaki sonucu alırız;

resim5

İstediğimiz sonucu aldık. Uygun bir değer girilmediği için kullanıcıya hata mesajı verildi. Fiyat field'ına değer ataması yapılmadığından dolayı da, ekrana yazdırdığımızda default değeri olan 0'ı görmekteyiz. Eğer uygun bir değer atansaydı, ekranda atadığımız değeri görebilecektik.



Merhabalar. Bu yazımda Visual Studio kullanmadan basit bir Console uygulaması yapacağız. C# kodlarımı notepad kullanarak yazacağım ve kodlarımı Visual Studio Command Prompt kullanarak derleyeceğim. İlk olarak C’nin içine “proje” isimli bir klasör açarak, txt uzantılı bir dosya (text document veya metin belgesi) oluşturalım. Ben dosyaya “ilkUygulama.txt” adını verdim. Daha sonra oluşturduğumuz text dosyası içerisine aşağıdaki kodları yazalım;

     class Programim

     {

          static void Main()

               {

                    System.Console.WriteLine("Hello World");

                    System.Console.ReadLine();

               }

     }

(Visual studio ile açtığımız console uygulamalarında proje, hazır olarak using blokları ve main metodu yazılmış olarak başlatılır. Yukarıdaki kod bloğunda using bloklarını yazmadan tipin tam adını yazarak erişimi sağladım.)

Oluşturduğumuz text dosyasına yukarıda yazmış olduğum kodları yazarak, dosyayı kaydedelim. Daha sonra başlat menüsünden programlar->visual studio 2010-> visual studio tools yolunu takip ederek Visual Studio Command Prompt penceresini açalım. Command Prompt’u açtığımızda aşağıdaki siyah ekran karşımıza çıkmış olmalı:

Untitled11

Yukarıdaki pencere açıldıktan sonra, kaynak kodumuzu derleme işlemine başlayabiliriz.

Benim kod dosyam C’nin içerisinde proje isimli klasörde kayıtlı olduğu için C:/ üzerinden gideceğim. Bu sebepten ötürü ilk olarak projemizin kayıtlı olduğu yolu belirtmemiz gerekiyor.. Bunun için “cd\” komutunu kullanalım ve ardından kod dosyamızın kayıtlı olduğu yolu belirtelim.

Untitled22

Yukarıda gördüğümüz gibi, artık projemizin kayıtlı olduğu yol üzerinden ilerleyebiliriz.

Doğru yol üzerinde olduğumuza göre artık kod dosyamı c# derleyicisi olan csc’ye (c sharp compiler) verebiliriz. Bunun için “csc /t:exe ilkUygulama.txt” komutunu çalıştırmamız gerek. Burada “csc” derleyicimizin adını, “/t” komutu ise oluşacak assembly’nin türünü belirtmektedir.

Untitled33

Yukarıda belirttiğim kodu yazdığımız zaman, şekilde görüldüğü gibi .exe uzantılı assembly’mizi oluşturmuş olduk. Önceki yazımda bahsettiğim gibi, oluşan bu assembly içerisinde IL kodlar bulunmaktadır. Exe’ye çift tıklayarak programı çalıştırdığımız takdirde CLR devreye girer ve assembly’deki IL kodları yorumlayarak işletim sisteminin anlayacağı şekle dönüştürür ve aşağıdaki sonucu elde ederiz.

Untitled44



.Net, yazılım geliştirmek amacı ile Microsoft tarafından sağlanan bir platformdur. Bünyesinde Visual Studio, .net framework, expression ailesi gibi birçok ürünü barındıran .Net'i, tüm bu ürünlerin tek bir çatı altında toplandığı platform olarak da tanımlayabiliriz.

.Net ile temel olarak Console, Windows, Asp.Net, WPF ve Silverlight uygulamaları geliştirilebildiği gibi Mobile uygulamaları da geliştirilebilir (Windows Mobile işletim sisteminin yüklü olması gerekir). Ancak .Net platformunun temel amacının veritabanı programcılığı olduğunu söylersek yanılmış olmayız.

Mimariye geçmeden önce birkaç temel kavramı tanımlamakta fayda var.

Source Code : Bildiğimiz gibi bilgisayar 0’lar ve 1’lerden anlayan bir makinedir. Bilgisayara bir şeyler yaptırmak istersek onun anlayacağı dil ile, yani 0 ve 1 rakamları ile konuşmalıyız. Günümüz teknolojisinde, programlama dillerinin görevi bu işi kolaylaştırmaktır. Belirli syntax kurallarına uyarak insanlara bir şeyler ifade eden kodlamalar yapmak mümkün. Bir şeyler ifade etmesinden kasıt, baktığımızda ne işe yarayabileceği hakkında fikir sahibi olabilmemizdir. İşte bize anlam ifade eden ve bir arayüz kullanarak yazdığımız bu kodlara kaynak(soruce) kod adını veririz.

Native Code : Kaynak kodların derlenerek makinenin anlayacağı şekle dönüşmüş haline native code adını veririz.

Compile : Kaynak kodların, makinenin anlayacağı dile veya bir ara dile dönüştürülmesi işleme compile(derleme), bu işlemi gerçekleştiren araca da compiler(derleyici) adını veririz.

Örnek olarak C programlama dili ile yazılmış bir kodun derlenmesi olayını inceleyelim :

resim1

Şekilden de anlaşıldığı gibi, yazılan kaynak kodlar bir derleyici aracılığı ile direkt olarak native code’a, yani makinenin anlayacağı dile dönüştürülmektedir. Bu işlem sırasında bir problem ile karşı karşıyayız. Kaynak kodlar ara işlemden geçmeden direkt olarak native code’a dönüştürüldüğü için oluşan native kod yalnızca o işletim sistemine özeldir. Mesela programımızı 32 bit Windows 7 işletim sisteminde derlediğimizi varsayalım. Oluşan program, çift tıklandığında çalışan bir exe olsun. Derlediğimiz makine üzerinde programa çift tıkladığımızda program çalışacaktır. Ancak bu exe dosyasını alıp 64 bit Windows 7 işletim sistemi üzerinde çalıştırmayı denediğimiz zaman program çalışmayacaktır. Bunun nedeni oluşan native code’un yalnızca 32 bit Windows 7 işletim sistemine özgü olmasıdır. Yazılan kaynak kod farklı işletim sistemlerinde derlendiğinde ortaya çıkan native code’lar birbirinden farklıdır.

Şimdi .Net platformunda yazılıp derlenen bir programın çalışmasını inceleyelim:

resim2

.Net bünyesinde mevcut olan programlama dillerini kullanarak oluşturduğumuz kaynak kodu derlediğimiz zaman, kaynak kod hangi dil ile yazılmışsa o dile ait derleyici tarafından (visual studio ilk kurulduğunda hangi programlama dilini kullanacağımızı belirtmemizi ister) derlenir ve assembly oluşur. Assemly, uzantısı .exe veya .dll olan fiziksel bir dosyadır. Assembly içerisinde bulunan IL kodlar makine üzerinde çalıştırılabilir bir kod bloğu değildir.

Assembly içinde;

1)Manifest Dosyanın adı, açıklaması, versiyon numarası ve en önemlisi referans edilen dll dosyalarının listesinin bulunduğu bölümdür. Kısaca, programın kendisi ile ilgili kimlik bilgisidir diyebiliriz.
2)Metadata : Program içerisinde kullanılan tiplerin, sınıfların, üyelerin bulunduğu bölümdür.
3)CIL : Kaynak kodun derlenmesiyle oluşan ve tüm diller için ortak olan IL kodlarının bulunduğu bölümdür. Sözde kod olarak da adlandırılırlar.
4)Resources : Programın kullandığı ses, video, resim vs dosyalarının bulunduğu bölümdür. Her assembly içinde bulunması zorunlu değildir.

Kaynak kodların, kendi derleyicileri ile ortak bir dile çevrilmeleri, dil bağımsızlığını sağlayan en önemli etmendir. Yani programı ister c#.net ile, ister vb.net ile, ister c++.net ile yazalım, oluşan IL kodlar aynı olacaktır. Daha sonra oluşan bu kodlar CLR tarafından yorumlanarak native code’a dönüştürülür. CLR’nin olduğu her ortamda, önceden oluşturulmuş bu IL kodların, o sisteme uygun bir şekilde yorumlanması ile aynı sonucu elde etmek mümkündür. Burada CLR’ın platform bağımsızlığını sağladığını söylersek doğru bir tespitte bulunmuş oluruz.

Ekrana “Hello World” yazdıran basit bir program yazdığımızı düşünelim. .Net bünyesinde bulunan herhangi bir programlama dilini kullanarak kaynak kodları oluşturup derlediğimizi varsayalım. Programı derlediğimiz zaman ortaya çıkan fiziksel dosyanın assembly olduğundan yukarıda bahsetmiştik. Bu oluşuma kadar olan kısım “derleme zamanı (compile-time)” olarak adlandırılır. Yani kaynak kodumuzu yazdık, derledik ve ortaya bir assembly çıktı. Ortaya çıkan assembly çift tıklandığında ise “çalışma zamanı (run-time)” süreci başlar. İşte bu noktada CLR devreye girer ve assembly içerisinde bulunan IL kodları yorumlayarak native code üretir. Oluşan native code, bilgisayar tarafından çalıştırılır ve console ekranında “Hello World” yazısını görürüz.

Debugging

Yazılan programların(büyük projeleri varsayıyorum) sıfır hata ile yazılması imkansızdır. Boyutu değişken olmakla birlikte mutlaka hatalarla karşılaşılır. Zaten bu sebepten dolayı projelerin en önemli aşamalarından biri de bakım aşamasıdır. İşte yazılımlar içerisinde ortaya çıkan bu hatalara bug adını veririz .Debug kelimesini de hata ayıklamak olarak düşünebiliriz. Çalışan bir programın çalışmasını adım adım izleyebilmemizi ve bu esnada istediğimiz sonuçları elde edip edemediğimizi görmemizi sağlayayan bir özelliktir. Debugging işlemi ancak managed platformlar üzerinde gerçekleştirilebilir. Managed plaformu şu şekilde tanımlayabiliriz; yazılan kaynak kodların direkt olarak native koda değil de ortak bir ara dile çevrildiği, ve o ara dilin, başka bir araç tarafından yorumlanarak native koda çevrildiği platformlardır. .Net içerisinde debug kelimesini duyduğumuzda aklımıza ilk gelmesi gereken kavram, oluşan IL kodları işletim sistemi için yorumlayan CLR olmalıdır. Çünkü programa debug yeteneğini kazandıran araç CLR’dır.

CLR’ın 4 temel işlevi vardır:
1) CIL kodları Native Code’a çevirmek.
2) Bellek yönetimini sağlamak.
3) Thread yönetimini sağlamak.
4) Debugging



C# dili nesne yönelimli bir programlama dili olduğundan ötürü, nesneler çok büyük bir öneme sahiptir. C#'te herşey bir nesnedir. Nesne kavramını duyduğumuzda aklımıza gelen ilk şey Constructor(yapıcı) metottur. Çünkü bir nesnenin örneklenip Ram'e çıkabilmesi için constructor metot çağrısı yapılması gerekir.

Constructor metodun özellikleri ;

1-Geriye değer döndürmez.

2-İsmi,tiple aynı olmak zorundadır.

3-istenildiği bir anda çağrısı yapılamaz. Özel bir çağrılma biçimi vardır.

4-Nesne üretmek için çağırılan metottur. Constructor metot çağırılmadan heap'te nesne üretilemez.

C#'ta belki de en önemli anahtar sözcük "new" anahtar sözcüğüdür. Bu anahtar sözcüğün yazılması demek, nesne üretmek anlamına gelmektedir. İşte tam bu noktada Constructor kavramı devreye girer. Yukarıda da bahsettiğim gibi, nesne üretmek için Constructor metot çağrısı yapılmak zorundadır. "new" anahtar sözcüğü ile bu çağrıyı yapmış oluruz. Özel bir çağırma biçimi olmasından kasıt aslında budur.

Neden constructor metot yazarız;

1-Nesneye ilk değer atamaları yapabilmek.

2-Nesne kullanıcısını, belli değerleri vermesi konusunda zorlamak.

3-Nesne oluşurken yapılacak ekstra işleri kodlamak.

Elimizde bir Urun sınıfı olsun.

class Urun

        {

            public int Id;

            public string Ad;

            public double Fiyat;

        }

Sadece id, ad ve fiyat alanlarına sahip bu Urun sınıfının nesnesini örneklemek istersek;

 

class Program

        {

            static void Main(string[] args)

            {

                Urun u1 = new Urun();

            }

        }

Girişte bahsettiğim gibi, "new" anahtar sözcüğü nesne üretmek anlamına gelir. Dolayısı ile burada aklımıza gelen ilk şey, bir Urun nesnesi oluşturmak için Urun sınıfının Constructor metodunu çağırmamız gerektiğidir. Zaten new anahtar sözcüğü ile bu metodu çağırmış oluyoruz. Dikkat ederseniz Urun sınıfına Constructor metot yazmadım ve nesne üretmek için bu metoda ihtiyacımız olduğunu da söylemiştik. Peki yapıcı metot yazılmadığı halde nasıl nesne üretildi?

-Bir class(sınıf) ya da struct(yapı) içerisinde contructor yazılmamışsa, default(varsayılan) olarak o tipin parametre almayan constructor'ı çağrılır.

public Urun()

{

}

Yani, Urun sınıfında constructor metot yazmadığımız zaman yukarıda gördüğümüz constructor metot default olarak yazılacaktır. Oluşan assembly'nin IL kodları incelendiğinde oluşan constructor metodu görmemiz mümkündür. Özelliklerinde bahsettiğimiz gibi, ismi Urun tipi ile aynıdır.

ctor8

Şimdi de nesne oluşturulurken, field'lara değer atama konusunda kullanıcıyı zorlayan bir constructor metot yazalım;

class Urun

        {

            public int Id;

            public string Ad;

            public double Fiyat;

 

            public Urun(int id, string ad, double fiyat)

            {

                Id = id;

                Ad = ad;

                Fiyat = fiyat;

            }

        }

 

Artık new anahtar sözcüğü ile nesneyi örneklerken; id,ad ve fiyat alanlarına ilk değerlerini atamak zorundayız.

class Program

        {

            static void Main(string[] args)

            {

                Urun u1 = new Urun(1, "Monitör", 200);

            }

        }

Yukarıda, u1 nesnesini, Urun metoduna, yani constructor metoda, istediği parametreleri vererek oluşturduk.

Burada önemli bir nokta var. Artık Urun sınıfımda, 3 tane parametre alan bir yapıcı metot tanımlanmış durumda. Şimdi ilk örnekte yaptığımız gibi, ilk değer ataması yapmadan nesne örneklemeye çalışalım.

ctor6

Yukarıdaki kodu derlemeye kalktığımızda compiler'dan red cevabı alarak, bir hata ile karşılaşacağız;

ctor7

Anlamı şudur:"Urun sınıfında parametre almayan bir Constructor Metot yok". Buradan çıkaracağımız sonuç;

-Tasarladığımız tip içerisine bir Constructor metot yazılmışsa, default(varsayılan) olarak, yazılan bu metot çağırılır. Eğer yukarıdaki gibi parametre vermeden bir Urun nesnesi oluşturmak istersek, Constructor'ı overload ederek parametre almayan versiyonunu da tanımlamamız gerekir.



"Static" sıkça karşılaştığımız bir anahtar sözcüktür(keyword). C#'da kullandığımız bütün sınıflar(class), ve üyeleri(metot, field, property, delegate, event, constructor) için olmassa olmaz bir kavramdır. Bir sınıf veya üye tanımlanırken static anahtar sözcüğü yazılırsa, o sınıfa veya üyeye, static'tir deriz. Eğer yazılmamışsa non-static'tir deriz. (Burada constant; -diğer bir adı ile değişmezler veya sabitler- istisnai bir duruma sahiptir. Constant üyelere static anahtar sözcüğünu yazamayız. Çünkü default olarak static tanımlanırlar. Tanımlarken static yazmaya zorlarsak derleme zamanı hatası alırız.)

Bir Sınıfın Static Olması

Bir sınıf static olarak tanımlanıyosa, içerisindeki bütün üyeler static olarak tanımlanmak zorundadır. Amaç tüm üyeleri static olmaya zorlamaktır. Static sınıfların özellikleri aşağıdaki gibidir:

-C# 2.0 ile birlikte static anahtar sözcüğü class'lara da uygulanabilir bir hale geldi..
-Bir class'a static anahtar sözcüğü uygulandığından içindeki tüm üyeleri static olmaya zorlamış oluruz...
-Tek bir özel nesne örneği, sınıfın kullanıldığı anda ram'in Static bölgesine çıkar...
-Static sınıflar new ile örneklenemezler. Dolayısıyla heap'te nesne örnekleri bulunmaz.

Üyelerin Static Olması

C#’da bir sınıfa ait üyeler; metotlar, alanlar(field), özellikler(property), olaylar(event), delegeler(delegate), constructor(yapıcı) metot olarak sıralanabilir. Bu makalede metotlar ve alanlar üzerinde duracağız. Bir üyenin static olması demek; o üyeye erişimin, SınıfAdi.UyeAdi olarak yapılabilmesi anlamına gelmektedir. Bir üyenin non-static olması demek; o üyeye ancak ve ancak, tanımlandığı sınıfa ait bir nesne örneği üzerinden erişebilmemiz anlamına gelir.

ÖNEMLİ NOT: Static, bir erişim belirleyici değildir!

Bir üye neden static olmalıdır? Aynı şekilde, bir üye neden non-static olmalıdır? Şimdi bu sorulara örnek seneryolar aracılığı ile cevap bulmaya çalışalım.

-Static Field(Alan)

Öncelikle, yeni bir console uygulaması açalım. Daha sonra uygulamaya, adı “Urun” olan yeni bir sınıf ekleyelim. Bu sıfın içerisinde de Id, Ad ve Fiyat bilgilerini tutalım. Seneryomuza göre, oluşan ürün nesneleri için kdv bilgisini de tutmak istiyoruz. Dolayısıyla oluşturduğumuz sınıfa kdvOran alanını da ekleyelim. Benim oluşturduğum Urun sınıfının kodları aşağıdaki gibidir;

class Urun

        {

            public int Id;

            public string Ad;

            public double Fiyat;

            public double KdvOran;

        }

Yukaridaki kodlara baktığımızda, alanların non-static olduklarını söyleyebiliriz. Yazının girişinde bahsettiğim gibi, eğer static anahtar sözcüğü yazılmamışsa, o üye non-static demektir. Üyenin non-static olması da; üyeye erişimin, ancak Urun tipinden bir nesne örneği üzerinden yapılabileceği anlamına gelmektedir. Şimdi 2 tane Urun nesnesi oluşturarak bu alanlara değer atayalım;

 

class Program

        {

            static void Main(string[] args)

            {

                Urun urun1 = new Urun();

                urun1.Id = 1;

                urun1.Ad = "Monitör";

                urun1.Fiyat = 200;

                urun1.KdvOran = 0.18;

 

                Urun urun2 = new Urun();

                urun2.Id = 2;

                urun2.Ad = "Klavye";

                urun2.Fiyat = 80;

                urun2.KdvOran = 0.18;

            }

        }

 

Oluşturduğumuz urun1 ve urun2 nesnesinin RAM(Bellek) üzerindeki görüntüsü sembolik olarak şu şekildedir;

 

resim3

Dikkat ederseniz Id, Ad ve Fiyat bilgisi her nesne örneği için özel bir değerdir. Yani urun1 nesnesini, urun1 yapan özelliklerdir. urun2 nesnesi için de aynı şeyi söylememiz mümkündür. Ancak KdvOran alanında göze çarpan önemli bir nokta var. Bu alanın değeri her nesne örneği için özel bir değer değildir, kdv değeri genel bir değerdir ve tüm Urun nesneleri için ortaktır. Dolayısı ile yukarıdaki kod bloğunu göz önünde bulundurursak, Kdv bilgisinin her Urun nesnesi için tekrar tekrar tanımlandığını ve tekrar tekrar değerinin atandığını görürüz. Bu örnekte elimizde 2 adet Urun nesnesi olduğundan, bu bir problem olarak algılanmayabilir. Ancak elimizde 100000 adet Urun nesnesi olsaydı, 100000 kere aynı işi tekrarlamış olacaktık ve kötü bir programlamcılık yapmış olacaktık. Kodlarımızı yazarken bu tarz detaylara önem vermemiz; hem performans, hem de kod okunurluğu açısından önemli bir avantaj sağlar.

Şimdi gelelim doğru tasarımı yapmaya. Urun sınıfı tasarımımızı aşağıdaki gibi değiştirelim;

class Urun

        {

            public int Id;

            public string Ad;

            public double Fiyat;

            public static double KdvOran;

        }

KdvOran alanının tüm Urun nesneleri için ortak olduğu konusunda hem fikir olduğumuzu düşünüyorum. İşte tam bu noktada “static” anahtar sözcüğü imdadımıza yetişecektir. KdvOran alanını static olarak tanımladığımız takdirde; artık her nesne örneği için özel bir alan olmaktan çıkıp, o sınıfa ait genel bir alan haline gelecektir. Yani her nesne örneği için tekrar tekrar kdv alanı oluşturup değer atamak yerine, static bir alan olan KdvOran’a bir kere değer ataması yapmamız yeterli olacaktır. Artık istediğimiz yerde Urun.KdvOran (SınıfAdı.UyeAdı) diyerek bu alanı kullanabiliriz.

Yukarıdaki sınıf tasarımından sonra nesne örneklememizde artık urun1.KdvOran veya urun2.KdvOran diyemeyeceğiz. Çünkü Urun tipinden olan nesne örnekleri üzerinden, sadece o sınıfa ait non-static üyelere ulaşabiliriz. Nesne örneklerken kullanacağımız kodlar ;

class Program

        {

            static void Main(string[] args)

            {

                Urun urun1 = new Urun();

                urun1.Id = 1;

                urun1.Ad = "Monitör";

                urun1.Fiyat = 200;

 

                Urun urun2 = new Urun();

                urun2.Id = 2;

                urun2.Ad = "Klavye";

                urun2.Fiyat = 80;

 

                Urun.KdvOran = 0.18;

            }

        }

Yaptığımız son sınıf tasarımına göre, nesnelerin RAM üzerindeki sembolik gösterimi şu şekilde olur ;

 

resim6

Bu seneryodan yola çıkarak şu tanımı yapmamız mümkündür:

-Bir alan(field); o sınıfa ait her bir nesne örneği için özel bir alan ise, non-static olarak tanımlanmalıdır. Eğer her nesne örneği için ortak bir alan ise static olarak tanımlanmalıdır.


-Static Metot

Şimdi de metotların static veya non-static olma durumlarını inceleyelim. Yukarıda tasarladığımız Urun sınıfı üzerinden devam edelim.

Urun sınıfımıza ZamYap adlı bir metot eklemek istiyoruz. Metot geriye değer dönmesin ve parametre olarak yapılacak zam oranını alsın. Bu noktada düşünmemiz gereken ilk nokta şu olmalı, static mi non-static mi? Aslında bu metot hem static hem de non-static olarak yazılabilir. Ancak hangisinin daha doğru olduğuna, ikisini de deneyerek karar verelim.

İlk olarak Urun sınıfı içine static olarak ZamYap metodunu tanımlayalım. Metodu Urun sınıfının içine yazmamızın sebebi, yapılan işin Urun nesnelerinin işleri olmasıdır. Yani bu metodu Program sınıfının içine yazmak anlamsız olacaktır. Urun sınıfının içindeki ZamYap metodunun kodları aşağıdaki gibidir;

public static void ZamYap(Urun u, double zamOran)

{

    u.Fiyat += u.Fiyat * zamOran;

}

Dikkat ederseniz, metoda Urun tipinden bir parametre ilave edilmiş durumda. Bunun nedeni gayet basit; metot static olduğundan dolayı, erişim Urun.ZamYap şeklinde yapılmalıdır. Peki bu metot hangi ürün nesnesi için çalışığını nereden bilecek? İşte bu yüzden Urun tipinden bir parametre vermek zorundayız ki, hangi ürünün fiyatına zam yapacağını belirtebilelim.

Program tarafında metodun çağırılması da şu şekilde olur;

class Program

        {

            static void Main(string[] args)

            {

                Urun urun1 = new Urun();

                urun1.Id = 1;

                urun1.Ad = "Monitör";

                urun1.Fiyat = 200;

 

                Urun.ZamYap(urun1, 0.20);

                Console.WriteLine(urun1.Fiyat);

            }

        }

Yukarıdaki kod çalıştırıldığında urun1’in fiyatının 240 olduğunu görürüz.

Peki bu doğru bir kodlama oldu mu dersiniz? İşte bu noktada düşünmemiz gereken en önemli nokta şu olmalı; bu metot her Urun nesnesi için özel bir iş mi yapmaktadır, yoksa genel bir iş mi yapmaktadır? Cevap ortadadır, bu metot her ürün nesnesi için özel bir iş yapmaktadır. Yani hangi ürün için çağırılırsa, o ürün için özel olan “Fiyat” alanını kullanarak o ürünün fiyatına zam yapmaktadır. İşte bu yüzden yukarıdaki kodlama, istediğimiz sonucu almamıza rağmen doğru bir kodlama değildir. Bu metot her nesne için özel bir iş yaptığından dolayı non-static olarak tanımlanmalıdır.

Doğru tasarım şu şekilde olmalıdır;

public void ZamYap(double ZamOran)

{

    Fiyat += Fiyat*ZamOran;

}

Dikkat ederseniz static olarak tanımlanan metotta olan Urun parametresine artık gerek kalmadı. Çünkü metot non-static olduğundan dolayı, bir Urun sınıfı nesnesi üzerinden çağırılmak zorundadır. Bir diğer çok önemli nokta; metodun içerisinde, direkt “Fiyat” diyerek eriştiğim alan, metot hangi nesne için iş yapıyorsa o nesnenin “Fiyat” alanıdır. Metoda urun1.ZamYap diyerek erişeceğimden dolayı, metodun içerisindeki "Fiyat" alanı, urun1'in fiyat alanıdır. Buradan şöyle bir sonuca varmak mümkündür; bir sınıf içerisindeki non-static metotlar, o sınıfa ait non-static veya static alanları direkt olarak kullanabilirler. Ancak bir sınıf içerisindeki static metotlar , yalnızca o sınıfa ait static alanları kullanabilirler.

Şimdi program tarafında metodun çağırılmasına bakalım;

class Program

        {

            static void Main(string[] args)

            {

                Urun urun1 = new Urun();

                urun1.Id = 1;

                urun1.Ad = "Monitör";

                urun1.Fiyat = 200;

 

                urun1.ZamYap(0.20);

                Console.WriteLine(urun1.Fiyat);

            }

        }

Metodun bu haliyle, daha doğru bir kodlama yaptığımızı görmek mümkün. artık her Urun nesnesi için özel bir iş yapan ZamYap metodu, hangi nesne için çağırılıyorsa o nesne üzerinden çalışmaktadır.

Şimdi de seneryomuza KdvOranDegisti adında bir metot tanımlayalım. Aklımıza ilk gelen şey, bu metodun her nesne için özel bir iş yapıp yapmaması sorusudur. Bu metodun amacı, static bir alan olan, yani Urun sınıfı için genel bir alan olan KdvOran değerini değiştirmektir. Dolayısı ile bu işin oluşan nesneler ile bir alakası yoktur. Bu sebepten ötürü metodu static olarak tanımlamalıyız;

public static void KdvOranDegisti(double YeniKdvOran)

{

    KdvOran = YeniKdvOran;

}

Metodu static olarak tanımladığımız için Urun.KdvOranDegisti(….) diyerek metoda erişim sağlayabiliriz. Yani herhangi bir Urun sınıfı nesne örneğine ihtiyaç duymayız.

Eğer bu metodu non-static olarak tasarlamış olsaydık (Yanlış tasarım);

public void KdvOranDegisti(double YeniKdvOran)

{

    KdvOran = YeniKdvOran;

}

Yukarıda yazılan metoda erişebilmemiz için bir Urun nesnesi üzerinden gitmemiz gerekecekti;

urun1.KdvOranDegisti(0.08);

urun2.KdvOranDegisti(0.18);

Buradan da anlaşılacağı gibi yapılan işin urun1 veya urun2 nesnesi ile hiçbir alakası yoktur. dolayısı ile bu metodun non-static olarak tanımlanması doğru bir tasarım değildir.

Sonuç => Bir sınıfa ait her nesne için özel bir iş söz konusu ise, metot non-static tanımlanmalıdır, Eğer yapılan iş genel bir iş ise, metot static olarak tanımlanmalıdır.