C#’ta Fonksiyon Üyeleri

C#’ta çalıştırılabilir kod içeren üyeler genellikle sınıfın fonksiyon üyeleri olarak adlandırılırlar. Bir önceki yazıda belirttiğim konu olan methodlar konusu da aslında bir çeşit fonksiyon üyesidir. Aşağıda tablo Liste<T> olarak tanımlanmış bir generic sınıfın bir çok fonksiyon üyesi örneğini gösterir:

public class Liste<T> {
const int varsayılanKapasite = 4; Sabit
T[ ] elemanlar;
int sayaç;
Alanlar
public Liste (int kapasite = varsayılanKapasite ) {
elemanlar = new T[kapasite];
}
Yapıcı
public int Sayma {
get { return sayma; }
}
public int Kapasite {
get {
return elemanlar.Length;
}
set {
if (value < sayma) value = sayma;
if (value != elemanlar.Length) {
T[] yenielemanlar= new T[value];
Array.Copy(elemanlar, 0, yenielemanlar, 0, sayma);
elemanlar = yenielemanlar;
}
}
}
Özellikler
public T this[int index] {
get {
return elemanlar[index];
}
set {
elemanlar[index] = value;
OnChanged();
}
}
İndexleyiciler
public void Add(T item) {
if (sayma == kapasite) kapasite = sayma * 2;
elemanlar[sayma] = item;
sayma++;
OnChanged();
}
Methodlar
public event EventHandler Changed; Olaylar (Events)
public static bool operator ==(Liste<T> a, Liste<T> b) {
return Equals(a, b);
}
public static bool operator !=(Liset<T> a, Liste<T> b) {
return !Equals(a, b);
}
Operatörler
}
  • Yapıcılar (Constructors)

C# hem örnek hem de static yapıcıları destekler. Örnek yapıcılar, sınıfın ilk kullanımına hazırlamak için gerekli olan uygulama üyeleridir. Static yapıcılar, bir eylem gereken örnek sınıfın ilk yüklendiği zaman gerekli olan uygulama üyeleridir.

Bir yapıcı aynı bir method gibi tanımlanır fakat geri dönüş tipi olmamalı ve içerdiği sınıf ile aynı isme sahip olmalıdır. Eğer bir yapıcı, static anahtar kelimesi ile tanımlanmışsa o static yapıcıdır, aksi taktirde örnek yapıcı olarak adlandırılırlar.

Örnek yapıcılar aşırı yüklenebilir. Örneğin, Liste<T> adında bir sınıfın iki yapıcısı tanımlansın; birisini hiçbir parametre almayan, diğeri de bir adet int parametre alan. Örnek yapıcılar new operatörü kullanılarak çağırılırlar.


Liste<string> liste1 = new Liste<string>();
Liste<string> liste2 = new Liste<string>(10);

Diğer üyelerin aksine, örnek methodlar miras almazlar. Yani bir sınıfın kendi içinde tanıtılmış örnek yapıcısından başka bir örnek yapıcısı olamaz. Eğer bir sınıfın örnek yapıcısı yoksa, parametresiz boş bir örnek yapıcı otomatik olarak yaratılır.

Önemli: Yapıcılar tembel olmalıdırlar. En iyi pratik olarak yapıcılar içinde minimum iş yapılamıdır ki bu da basit bir şekilde argümanların daha sonra kullanımı için basitleştirmelidir. Örneğin; bir dosyanın ismini ya da o dosyanın yolunu veritabanı için tutmak istiyorsunuz ama dış kaynakları gerekli olmadığı sürece açmak istemiyorsunuz. Bu pratik, gerekli en az kaynağa erişimede emin olmak için yardımcı olur.

  • Özellikler (Properties)

C#’ta özellikler, alanların (fields) doğal uzantılarıdır. İkisi de ilişkilendirildikleri tiplerile birlikteki üyelerin isimleriyle, ilişkilendirildikleri alanların sözdizimi ile ve özelliklerle isimlendirilirler. Fakat, alanlardan farklı olarak, özellikler depolanmış bellek göstermezler. Bunun yerine, özelliklerin erişimcileri vardır ki onların değerlerini okuma ya da yazmada kullanılan ifadeleri belirtmede kullanılırlar.

Önemli: İyi bir uygulama tekniği olarak alanlara (özelliklerin verilerine) her zaman Getter ve Setter ile ulaşmak önerilir. Eğer Getter veya Setter’larınıza fonksiyonel işlevsellikler eklemek isterseniz, kodunuzu herhangi bir şekeilde kırmadan bunu kolaylıkla yapabilirsiniz.

Yukarıdaki anlatıma bir örnek verelim: Hiçbir zaman alanlarınızı (fields) public olarak tanımlamayın;


class İnek
{
public int Süt; // Kötü
}

Eğer belleğin yanında herhangi bir katman istemiyorsanız, bırakın derleyici sizin yerinize uygulasın;


class İnek
{
public int Süt {get; set;} // İyi
}

Bu sayede, istemci özelliğe Getter ve Setter’ı bağlar ve buna istinaden aşağıdaki şekilde derleyicinin uygulamasına etki edebilirsiniz;


class İnek
{
bool SüteSahip = false;
int Süt;
public int Süt {
get
{
if (!SüteSahip)
{
Süt = SütMakinesiUygula();
SüteSahip = true;
}
return Süt;
}
set
{
TersSütMakinesiUygula(value);
Süt = value;
}
}
}

Ayrıca, aşağıdaki kullanım ile hesaplanmış değer sizin programınızda kullanılır;


class İnek
{
public İnek()
{
Süt = SütMakinesiUygula();
}
public int Süt { get; private set; }
...
}

Bu durumda, özelliği tekrar hesaplıyoruz, ki neye ihtiyacımız olduğunu bilmediğimiz durumlarda bu gereksiz bir davranıştır.

Önemli: Özelliklere erişim tıpkı alanlara erişim gibi gözükür insanlara. Ve kişiler de doğal olarak alanlara erişim ile aynı performans göstereceklerini tahmin ederler. Eğer bir erişim belirgin bir iş ihtiyacı duyuyorsa (bir dosyadan okuma, veritabanında bir query çalıştırma gibi) bir method olarak açılmalıdır, bir özellik olarak değil. Bunu çağıranlar methodun daha fazla iş yaptığını düşünürler.

Aynı sebepten dolayı, özelliklere erişimde tekrarlı çağırımlar (koda müdahele etmeden) aynı değeri döndürmelidirler. DateTime.Now bu tavsiyeye uymayan örneklere sahiptir.

Bir özellik, bir alan ile aynı şekilde tanımlanır. İstisna olarak, noktalı virgülle bitirmek yerine Get ve Set erişimcilerini { ve } arasında kullanarak tanımsal biçimde alanlardan ayrılırlar. Hem Get hem de Set erişimcisine sahip özellikler okunur-yazılır (read-write) özellik, sadece Get erişimcisine sahip özellikler sadece okunur (read-only) ve sadece Set erişimcisine sahip özellikler de sadece yazılır (write-only) özellikler olarak tanımlanır.

Bir Get erişimcisi parametresiz bir method ve özellik tipini geri dönüş tipi olarak uygulayan bir method ile uyuşur. Belirlenen hedefin atamalar dışında, bir özellik bir ifadeyi referans ediyorsa, özelliğin Get erişimcisi, özelliğin değerini hesaplamada çağrılır.

Bir Set erişimcisi, value adında bir parametre içeren ve geri dönüş tipi olmayan bir method ile uyuşur. Bir özellik belirli bir atamayı ya da ++ ya da – gibi operandları referans ediyorsa, Set erişimcisi yeni değeri içeren bir argüman ile çağrılır.

Liste<T> sınıfı iki adet özellik tanımlar, Sayma ve Kapasite, sırasıyla sadece okunur ve okunur-yazılır özellikler. Aşağıdaki örnek bunların nasıl kullanıldığına bir örnektir:


Liste<string> isimler = new Liste<string>();
isimler.Kapasite = 100;   // Set erişimcisi çağrılır.
int i = isimler.Sayma;      // Get erişimcisi çağrılır.
int j = isimler.Kapasite;  // Get erişimcisi çağrılır.

Alanlarda ve özelliklerde benzer olarak, C# hem örnek özellikleri hem de static özellikleri destekler. Static özellikler static anahtar kelimesi ile tanımlanır, static anahtar sözcüğü kullanılmayan özellikler örnek özelliklerdir.

Bir özelliğin erişimcileri (Get ve Set) sanal (virtual) olabilirler. Eğer bir özellik tanımlaması virtual, abstract ya da override anahtar sözcüklerine sahipse, o özelliğin erişimcilerine uygular.

Önemli: Eğer bir sanal özellik, private erişimciye sahipse, o erişimci CLR içinde sanal olmayan methodtur ve türetilmiş sınıf içerisinde aşırı yüklenemez.

Özellikler aşağıdaki erişimcilere sahiptirler;

Statik değiştirici static
Erişim değiştirici public, internal, private, protected
Kalıtımsal değiştirici new, virtual, abstract, override, sealed
Yönetilemeyen kod değiştiricisi unsafe, extern
  • İndexleyiciler (İndexers)
    İndexleyiciler, diziler gibi, nesneleri indexleyebilen üyelerdir. Bir indexleyici, bir özellik ile aynı şekilde tanımlanır, tek fark olarak [ ] arasında parametre listesi hariç. Bu parametreler, indexleyicinin erişimcisi için uygundurlar. Özellikler gibi, indexleyiciler de read-write, read-only, write-only ya da bir indexleyici virtual tanımlanabilir.

Aşağıdaki List sınıfı string parametresi olan basit bir indexleyici tanımlamıştır;


List<string> isimler = new List<string>();
isimler.Add("Ülkü");
isimler.Add("Hülya");
isimler.Add("İlknur");
for (int i = 0; i < isimler.Count; i++) {
string s = isimler[i];
isimler[i] = s.ToUpper();
}

İndexleyiciler aşırı yüklenebilirler. Bunun anlamı sınıflar istedikleri uzunlukta parametre listeleri içeren indexleyici tanımlayabilirler.

  • Olaylar (Events)

Bir olay, sınıflara ya da nesnelere bildirim sağlanmasına olanak veren üyelerdir. Olaylar, tıpkı alanlar gibi tanımlanırlar. Fark olarak event anahtar kelimesi kullanılır ve tipi delegate tipi olmalıdır.

Jesse Liberty: Gerçekte, event anahtar sözcüğü sadece C#’ta delegate’in kullanılabilir olmasına bir yol göstericidir.

Chris Sells: event anahtar sözcüksüz, şunu yapabilirsiniz;


delegate void CalismaTamamlandi();
class Calisan
{
public  CalismaTamamlandi Tamamlandi; // Delegate alanı, bir event değil.

}
class  Patron
{
public void CalismaTamamlandi() { }
}
class Program
{
static void Main()
{
Patron Soner = new Patron();
Calisan isci = new Calisan();
isci.Tamamlandi += Soner.CalismaTamamlandi(); // Yapmak istediğiniz
isci.Tamamlandi = Soner.CalismaTamamlandi();  // Derleyicinin izin verdiği
}
}

Ne yazık ki, event sözcüğü ile birlikte, Tamamlandi() sadece delegate tipinde public bir alandır. Derleyici için de sorun oluşturmaz bu durum. Fakat event anahtar sözcüğünü eklediğimizde, += ve –= gibi operatörleri sınırlandırırsınız.


class Calisan
{
public event CalismaTamamlandi Tamamlandi;

}

isci.Tamamlandi += Soner.CalismaTamamlandi(); // Derleyici izin verir.
isci.Tamamlandi = Soner.CalismaTamamlandi(); // Derleyici hata verir.

Burada event sözcüğünün kullanımı sadece bir kere public bir alan oluşturmanın doğaln olmasıdır.Çünkü derleyici güvenli operatörlerin kullanımını daraltır.

Olay üyesi tanımlayan bir sınıf içerisinde, olaya delegate tipindeki bir alana erişimi gibi erişilebilir. Alan, olaylara eklenen olay tutucuları (event handler) temsil eden delegate’e bir referans saklar. Eğer herhangi bir event handler mevcut değilse, o alanın değeri null olur.

List<T> sınıfı basit bir olay üyesi olan Changed tanımlar ki bu yeni elemanların listeye eklenmesini gösterir. Changed olayı, OnChanged sanal methodundan kullanılır ki bu ilk önce olayın boş olup olmadığını kontrol eder. (Bunun anlamı hiçbir event handler’ın olmaması)

İstemciler olaylara olay tutucular (event handler) ile tepki gösterirler. Event handler’lar += operatörü ile eklenir ve –= operatörü ile silinebilirler. Aşağıdaki örnek, bir event handler’ın List<string>’in Changed olayına eklenmesini gösterir;


using System;
using System.Collections.Generic;

class Test
{
static int DegismeSayisi;
static void ListChanged(object sender, EventArgs e)
{
DegismeSayisi++;
}
static void Main()
{
List<string> isimler = new List<string>();
isimler.Changed += new EventHandler(ListChanged);
isimler.Add("Ülkü");
isimler.Add("Hülya");
isimler.Add("İlknur");
Console.WriteLine(DegismeSayisi); // Çıktı 3 olur.
}
}

Chris Sells: isimler.Changed += new EventHandler(ListChanged); yerine isimler.Changed += ListChanged; şeklinde bir kısaltma da kullanılabilir.

  • Yok Ediciler (Destructors)

Bir destructor, bir sınıf örneğini ya da nesnesini yoketmek için kullanılan uygulamaları içerir. Yokedicilerin parametreleri yoktur, erişim kelimelerine sahip değillerdir ve açık bir şekilde çağrılamazlar. Yokediciler otomatik olarak garbage collection zamanında çalıştırılırlar.

Garbage collector, nesnelerin ne zaman toplanacağına ve yokedicilerin ne zaman çalıştırılacağına imkan verir. Özellikle, yokedicilerin çağrılma zamanların rastgele değildir, ve yokediciler herhangi bir thread zamanında çalıştırılabilir. Bu nedenle, sınıflar yokedicileri  sadece başka çözümler olmadığı zamanda uygulamalıdırlar.

Önemli: Yokediciler bazen “Finalizer” olarak anılırlar. Bu isim aynı zamanda Garbage collector API’sinde de görünür; örneğin: GC.WaitForPendingFinalizers.