Tag Archives: c#

DateTime.Parse(string) kullanmayı bırakamaz mıyız?

gandalf-parse

Scott Hanselman’ın What Great .NET Developers Ought To Know (More .NET Interview Questions) isimli yazısında da bahsedilği gibi, başta Stack Overflow olmak üzere birçok online sitede aşağıdaki şekilde kod örneklerine rastlıyorum:

DateTime dt = DateTime.Parse(myString);

Buna bir son verilmesi lazım. Bu kullanımda bir problem var. DateTime.Parse(string) metodu default olarak CurrentCulture ayalarını kullanmaktadır. Ve bu ayarlara istinaden direkt olarak bir string’i göndermek belirsiz durumlar oluşturmaya zemin hazırlar. Bu şu demek gibi;

Sana bir string gönderiyorum. CurrentCulture ayarlarına istinaden geçerli formatları sırasıyla dene, eğer biri uyuyorsa bu şekilde parse et. Eğer bu string için bu culture ayarlarında uygun bir format yoksa, bir FormatException fırlat.

Örnek olarak “01/02/2015” string’ini örnek alalım. Bu string hangi formatta? 1 Şubat 2015 mi? 2 Ocak 2015 mi? Cevap vermek imkansız zira bu sorunun cevabı dünyanın neresinde yaşadığınıza ve/veya hangi culture ayarlarını kullandığınıza göre değişir. Şehir/bölge olarak eşleştirilen Türkiye (“tr-TR”) ve Hindistan (“hi-IN”) için kullanılan culture ayarlarına ve bölgesel algıya göre bu string “1 Şubat 2015” olarak parse edilir fakat Amerika Birleşik Devletleri (“en-US”) için kullanılan culture ayarına göre “2 Ocak 2015” şeklinde parse edilir.

Bu DateTime.Parse(string) metodunun .NET Framework 4 versiyonu MSDN sayfasında (gördüğüm kadarıyla bu açıklama .NET Framework 4.5 ve 4.6 versiyonunda mevcut değil) da ifade edilir:

Because the Parse(String) method tries to parse the string representation of a date and time using the formatting rules of the current culture, trying to parse a particular string across different cultures can either fail or return different results. If a specific date and time format will be parsed across different locales, use the “DateTime.Parse(String, IFormatProvider)” method or one of the overloads of the ParseExact method and provide a format specifier.

Burada gördüğünüz gibi, spesifik bir format ile DateTime.ParseExact metodunu kullanmak çok daha kontrollü ve kesin bir sonuç döndürür. Tabi bu durumu gelen verinin formatını kesin olarak doğru bildiğimiz zamanlar kullanmalıyız. Eğer güvenmediğimiz veya bilmediğimiz bir yerden bu veriyi alıyorsak DateTime.TryParse veya DateTime.TryParseExact metodları daha uygun olur.

Açıklamada farklı sonuçlar oluşabileceği veya “fail” yani exception alabileceğimiz söyleniyor. Tabi ki bu çok normal. Zira “01/02:2015A22-22” gibi standart olmayan bir string gönderirsek, hangi culture ayarlarını kullanırsak kullanalım bir exception alırız ve bu exception’ı kod tarafıdan handle etmek zorunda kalırız. Standart olmayan veri dışında kullandığımız culture’ın DateTimeFormatInfo.Calendar özelliğinde var olan takvimin desteklediği sınırları aştığı durumlarda veya bu takvim üzerinde var olmayan bir tarihi parse etmeye çalıştığımızda da bu exception’ı alırız.

.NET Framework 4.5 versiyonunda çalıştırdığım şu kodu örnek alalım:

foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
    DateTime dt;
    if (!DateTime.TryParse("1500/12/29", culture, DateTimeStyles.None, out dt))
    {
        Console.WriteLine(culture.Name);
    }
}

Bu kod parçası normalde konsola harhangi bir şey yazdırmaz (eğer bu formatı desteklemeyen custom bir culture oluşturmadıysanız). Bu demek oluyor ki bu string formatı bir şekilde lokal makinanızdaki tüm culture ayarları tarafında parse edilebilir. Şimdi de string değerini “1500/12/30” olarak değiştirelim.

foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
    DateTime dt;
    if (!DateTime.TryParse("1500/12/30", culture, DateTimeStyles.None, out dt))
    {
        Console.WriteLine(culture.Name);
    }
}

Bu kod 6 adet sonuç üretiyor. TwoLetterISOLanguageName bakımından benzer olanları saymazsak bunlar “prs-AF” ve “yo-NG” değerleri. Peki bunların ortak özellikleri ne? Bu iki CultureInfo değeri DateTimeFormatInfo.Calendar olarak HijriCalendar kullanmakta. Ve bu takvimde 12. ay olan Zulhijjah (diğer adıyla Dhu al-Hijjah) sadece artık yıllarda 30 gün vardır. Diğer yıllarda 29 gün sürer bu ay. Bu arada HijriCalendar içerisinde Miladi takvime göre farklı bir artık yıl hesaplama kuralı vardır. Bu nedenle aslında bu takvimde var olmayan bir tarihi parse etmeye çalıştığımızdan exception almış oluruz. Son olarak string değerini “1500/12/31” olarak değiştirelim.

foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
    DateTime dt;
    if (!DateTime.TryParse("1500/12/31", culture, DateTimeStyles.None, out dt))
    {
        Console.WriteLine(culture.Name);
    }
}

Üstteki kod parçasında kullandığımız ”30” değeri için aldığımız sonuçlara ilave olarak “ar-SA” culture’ını da burada alırız. Neden? Çünkü bu culture değeri DateTimeFormatInfo.Calendar olarak UmAlQuraCalendar kullanmakta ve bu takvimin desteklediği maksimum tarih değeri “1500/12/30”. Bunu .NET Framework kodunu browser üzerinden gözlemleyebileceğiniz reference source içerisinde görebilirsiniz. Bu nedenle bu takvimin desteklediği tarih aralığı dışında bir tarih parse etmeye çalıştığımızdan exception almış oluruz. Aslında yukarıdakine benzer bir durum fakat yine de bahsetmek istedim.

    ////////////////////////////////////////////////////////////////////////////
    //
    //  Notes about UmAlQuraCalendar
    //
    ////////////////////////////////////////////////////////////////////////////
     /*
     **  Calendar support range:
     **      Calendar    Minimum     Maximum
     **      ==========  ==========  ==========
     **      Gregorian   1900/04/30   2077/11/17
     **      UmAlQura    1318/01/01   1500/12/30
     */

Bunların dışında DateTime.Parse(string) kullanımında, CurrentCulture konusunda kesin bir bilgi sahibi olunmasına kesin gözüyle bakamadığımızdan, “AM/PM” belirteçleri, tarih ayıracı, zaman ayıracı, gün isimleri, ay isimleri, kısaltılmış gün isimleri, kısaltılmış ay isimleri vb. özellikler culture bazlı farklılıklar gösterir. Örneğin; “Pazartesi” içeren bir string’i nasıl “en-US” culture’ı ile parse edemiyorsak, “Monday” içeren bir string’i de “tr-TR” culture’ı ile parse edemeyiz.

CurrentCulture ayarı konusunda kesin bilgimiz olsaydı bu herşeyi çözer miydi? Pek değil aslında. .NET Framework üzerindeki major ve minor değişiklikler veya işletim sistemi versiyonu değişikliklerinde bile CultureInfo özellikleri değişebiliyor. Bu konuda en önemli değişikliklerden biri it-IT culture’ı için .NET 3.5 versiyonundan 4.0 versiyonuna geçişte yaşandı. .NET 3.5 ve önceki versiyonlarda bu culture TimeSeparator olarak “.” kullanmasına rağmen .NET 4.0 versiyonu ile birlikte “:” kullanımına geçti. Date and time notation in Italy sayfasına göre de doğru olan “:” gibi görünüyor. Benzer durumlar Windows 10’a ilk geçiş sürecinde de yaşandı. Reddit’te benzer bir thread dahi mevcut. Ek olarak Culture data shouldn’t be considered stable (except for Invariant) yazısını da okuyabilirsiniz. Bu durum özet olarak CultureInfo sayfasında “Dynamic culture data” kısmında şu şekilde açıklanmış:

Except for the invariant culture, culture data is dynamic. This is true even for the predefined cultures. For example, countries or regions adopt new currencies, change their spellings of words, or change their preferred calendar, and culture definitions change to track this. Custom cultures are subject to change without notice, and any specific culture might be overridden by a custom replacement culture. Also, as discussed below, an individual user can override cultural preferences. Applications should always obtain culture data at run time.

When saving data, your application should use the invariant culture, a binary format, or a specific culture-independent format. Data saved according to the current values associated with a particular culture, other than the invariant culture, might become unreadable or might change in meaning if that culture changes.

Metinsel (string) bir veriyi Ticks bazlı bir veri yapısına (DateTime) parse etmenin karmaşıklığı yanı sıra, biz programcılar, kullandığımız culture ayarlarının bu gibi belirsiz değişimlere maruz kalabileceğini (InvariantCulture için bile olsa) unutmamalıyız. Bu nedenle nacizane fikrimce en iyi yaklaşım kendi static formatımızı oluşturmak veya değişimi düşük bir ihtimal olsa da InvariantCulture kullanmaktır. Yazdığımız bütün programların kendilerine özgü gereksinimleri olacaktır, bu yüzden bunları seçerken dikkkatli olmalıyız.

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.

C#’ta Interface (Arayüz) Kavramı

C#’ta arayüzler, sınıf ve struct yapılarının ne yapması gerektiğini belirten yapılardır. Bir arayüz; method, özellik (properties), olay (event) ve indexleyici (indexers) yapılarını içerebilir. Bir arayüzün içindeki metodların gövdeleri yoktur. Yani bir arayüz hiçbir zaman hiç bir şekilde bir uygulama sağlamaz. Bir arayüzü birden fazla sınıf ya da struct kullanabildiği gibi, bir sınıf ya da struct yapısı da birden fazla arayüz uygulayabilirler.

Arayüzler çoklu kalıtımı (multiple inheritance) desteklerler. Örneğin; aşağıdaki ICombobox arayüzü hem ITextBox hem de IListBox arayüzlerinden kalıtım alır:

interface IControl 
{ 
    void Boyama(); 
} 
interface ITextBox : IControl 
{ 
    void YazıBelirler(string text); 
} 
interface IListBox : IControl 
{ 
    void OgeBelirle(string[] items); 
} 
interface IComboBox : ITextBox, IListBox { }

Sınıflar ve struct yapıları da çoklu kalıtımı desteklerler. Aşağıda, EditBox sınıfı hem IControl hem de IDataBound arayüzlerinden kalıtım alır:

interface IDataBound 
{ 
    void Bind(); 
} 
public class EditBox : IControl, IDataBound 
{ 
    public void Boyama() { } 
    public void Bind() {}

}

Bir sınıf ya da struct belirli bir arayüzü uyguladığında, o sınıf ya da struct örneği, dolaylı olarak arayüz tipine çevrilebilir. Örneğin;

EditBox editBox = new EditBox(); 
IControl control = editBox; 
IDataBound dataBound = editBox;

Arayüzler aşağıdaki nedenlerden dolayı özeldirler;

  • Bir sınıf birden fazla arayüz uygulayabilir. Buna karşılık, bir sınıf sadece bir sınıftan kalıtım alabilir.
  • Arayüz üyelerinin tümü dolaylı olarak soyuttur (abstract). Buna karşılık, bir sınıf hem soyut hem de somut üyelerin uygulanmasına olanak sağlar.
  • Struct yapıların arayüzleri uygulayabilir. Buna karşılık, bir struct yapısı bir sınıf yapısından kalıtım alamaz.

Bir arayüz tanımlanması tıpkı sınıf tanımlamasına benzer, fakat bir arayüz kendi üyelerinin hepsi dolaylı olarak soyut oldukları için bunlara herhangi bir uygulama sağlamaz. Bu üyeler, bu arayüzü uygulayan sınıf ya da struct yapıları tarafından kullanılırlar.

System.Collections namespace’inde bulunan IEnumerator arayüzünün tanımı şu şekildedir;

public interface IEnumerator 
    { 
        bool MoveNext(); 
        object Current { get; } 
        void Reset(); 
    }

Bir arayüz üyesi her zaman dolaylı olarak public özelliktedir ve herhangi bir erişim değiştiricisi tanımlanamaz. Bir arayüzü uygulamak onun tüm elemanlarını public olarak kullanılmasına olanak sağlamak anlamına gelir.

internal class GeriSayim : IEnumerable 
{ 
    int sayac = 10; 
    public bool MoveNext() { return sayac-- > 0; } 
    public object Current { get { return sayac; } } 
    public void Reset() { throw new NotSupportedException(); } 

}

Dolaylı olarak bir nesneyi herhangi bir arayüz tipine çevirebilirsiniz;

IEnumerator e = new Countdown();

Bir arayüz başka bir arayüzden türetilebilir. Bu sayede türetilen arayüz diğer arayüzün tüm üyelerini kalıtım alır. Örneğin;

public interface IGeriAl 
     { 
         void GeriAl(); 
     } 
     public interface IYenidenYap : IGeriAl 
     { 
         void YenidenYap(); 
     }

Burada IYenidenYap, IGeriAl arayüzünün tüm üyelerini kalıtım alır.

Açık Arayüz Uygulaması (Explicit Interface Implementation)

      Birden fazla arayüz uygulaması bazen üye imzalarının çarpışması sonuçları doğurabilir. Bu durumu arayüz üyesine uygulanan

açıkça uygulama(explicitly implementing)

    şeklinde çözebilirsiniz. Şu aşağıdaki koda göz atalım;
class Test 
{ 
    interface I1 
    { 
        void Foo(); 
    } 
    interface I2 
    { 
        int Foo(); 
    } 
    public class Arac : I1, I2 
    { 
        public void Foo() 
        { 
            Console.WriteLine("I1.Foo uygulamasi"); 
        }

        int I2.Foo() 
        { 
            Console.WriteLine("I2.Foo uygulaması"); 
            return 23; 
        } 

    } 
}

Görüldüğü üzere, hem I1 hem de I2 arayüzlerinin çelişkili Foo imzaları var. Arac sınıfı, açık bir şekilde I2 arayüzünün Foo methodunu uygular. Bu durum bu iki methodun aynı sınıf içerisinde bir arada olmasına olanak verir. Bu üyelerin çağrımını ancak arayüz çevrimleri ile sağlayabiliriz:

Arac a = new Arac(); 
a.Foo();       // I1.Foo uygulanır. 
(I1)a.Foo(); // I1.Foo uygulanır. 
(I2)a.Foo(); // I2.Foo uygulanır.

Arayüz Üyelerini Sanal Uygulama (Implementing Interface Members Virtually)

Açıkça uygulanmış arayüz üyeleri default olarak sealed tanımlıdır. Temel sınıfta bu üyeler virtual ya da abstract olarak tanımlanmak zorundadır. Örneğin;

using System; 
using System.Collections; 
class Test 
{ 
    public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class Sınıf1 : IGeriAl 
    { 
        public virtual void GeriAl() 
        { 
            Console.WriteLine("Sınıf1.GeriAl()"); 
        } 
    } 
    public class Sınıf2 : Sınıf1 
    { 
        public override void GeriAl() 
        { 
            Console.WriteLine("Sınıf2.GeriAl()"); 
        } 
    } 
    public static void Main() 
    { 
        Sınıf2 s = new Sınıf2(); 
        s.GeriAl();                  // Sınıf2.GeriAl() 
        ((IGeriAl)s).GeriAl();  // Sınıf2.GeriAl() 
        ((Sınıf1)s).GeriAl();    // Sınıf2.GeriAl() 
    } 
}

Bir arayüz elemanını temel sınıftan doğrudan çağırmak ya da alt sınıftan arayüz çağrısı ile çağırmak bize aynı sonucu verir.

Altsınıflarda Arayüzü Tekrar Uygulama

Bir altsınıf, temel sınıf tarafından uygulanmış arayüz üyelerini tekrar tekrar uygulayabilir. Tekrar uygulama, üye uygulamasını kaçırır ve temel sınıftaki üyeler virtual olsun ya da olmasın çalışır. Aşağıdaki örnek ile daha iyi anlatmaya çalışalım;

Bu örnekte; TextBox sınıfı IGeriAl.GeriAl() methodunu açıkça uygular. Bu yüzden virtual olarak işaretlenemezler. Override etmek için, RichTextBox sınıfı IGeriAl.GeriAl() methodunu yeniden uygulamak zorundadır.

public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class TextBox : IGeriAl 
    { 
        void IGeriAl.GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        } 
    } 
    public class RichTextBox : TextBox, IGeriAl 
    { 
        public new void GeriAl() 
        { 
            Console.WriteLine("RichTextBox.GeriAl"); 
        } 

    }

Altsınıfın uygulamasında arayüz çağrısı olarak tekrar uygulanmış üyeleri çağırmak için;

RichTextBox r = new RichTextBox(); 
r.GeriAl(); //RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();  //RichTextBox.GeriAl

Peki ya TextBox sınıfımızı aşağıdaki şekilde değiştirirsek?

public class TextBox : IGeriAl 
    { 
        public void GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        }

    }

Bu özellik bize GeriAl() methodunu farklı bir şekilde çağırma olanağı sağlar.

RichTextBox r = new RichTextBox(); 
r.GeriAl();                   // RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();    // RichTextBox.GeriAl 
((TextBox)r).GeriAl();  // TextBox.GeriAl

Bu üçüncü durum bize gösteriyor ki, bu durumda bir uygulama yapılması bir üyenin base sınıftan değil de direkt arayüz tarafından çağrılması durumlarında etkin hale gelir. Bu genellikle istenilmeyen bir durumdur.

Arayüzler ve Boxing

Bir struct yapısını bir arayüz yapısına çevirmek boxing işlemidir. Dolaylı olarak uygulanmış struct yapısını çağırmak boxing işlemi değildir.

         interface I 
        { 
            void Metod(); 
        }

        struct S : I 
        { 
            public void Metod() { } 
        }

        S str = new S(); 
        str.Metod(); //Boxing değil

        I i = S; 
        i.Metod(); //Arayüz yapısına çevirdiğimizde boxing gerçekleşir.

Sınıf vs. Arayüz Yazılması

Temel olarak;

  • Uygulama aşamasında doğal olarak paylaşıllan tipler için sınıf ya da alt sınıflar kullanın.
  • Bağımsız uygulamaya sahip tipler için arayüzler kullanın.

Aşağıdaki sınıflar göz önüne alalım;

    abstract class Hayvan { } 
    abstract class Kus : Hayvan { } 
    abstract class Bocek : Hayvan { } 
    abstract class Kusgiller : Hayvan { } 
    abstract class Etobur : Hayvan { }

    //Somut Sınıflar

    class DeveKusu : Kus { } 
    class Kartal : Kus, Kusgiller, Etobur { } //Geçersiz 
    class Arı : Bocek, Kusgiller { } //Geçersiz 
    class Pire : Bocek, Etobur { } //Geçersiz

Kartal, Arı ve Pire sınıfları derlenmezler çünkü birden fazla sınıftan kalıtım almaları yasaktır. Bunu çözmek için, bazı tipleri arayüz tipine çevirmek zorundayız. Tabi burada soru şudur, hangi tipleri? Genel kuralımızı takip ederek, Bocek ve Kus sınıfları uygulamalarını paylaşırlar. Bu yüzden bunlar sınıf olarak kalmak zorundadır. Buna karşılık, Kusgiller uçmak için bağımsız bir mekanizmaya sahiptir ve Etobur et yiyenler için bağımsız stratejilere sahiptir. Bu yüzden bu sınıfları arayüz tipine çevirmemiz gerekir.

interface IKusgiller { } 
interface IEtobur { }

C#’ta Enum Tipine Genel Bakış

C#’ta enum tipi, sabitlerle isimlendirilmiş ayırt edici veri tipidir. Aşağıdaki örnek 3 sabit değer ile oluşturulmuş enum tipi olan Renk adında bir yapı tanımlayalım:

using System;

namespace ConsoleApplication1
{

enum Renk
 {
 Kırmızı,
 Yeşil,
 Mavi
 }

class Test
 {
 static void RenkBastır(Renk renk)
 {
 switch (renk)
 {
 case Renk.Kırmızı:
 Console.WriteLine("Kırmızı");
 break;
 case Renk.Yeşil:
 Console.WriteLine("Yeşil");
 break;
 case Renk.Mavi:
 Console.WriteLine("Mavi");
 break;
 default:
 Console.WriteLine("Bilinmeyen renk");
 break;
 }
 }

static void Main()
 {
 Renk r = Renk.Kırmızı;
 RenkBastır(r);
 RenkBastır(Renk.Mavi);
 }
 }
}
 

Her enum tipi underlying type adı verilen bir enum tipi ile ilişkilendirilir. Bir enum, underlying type içerisinde formatını ve mümkün olan değerin uzunluğunu tutar. Varsayılan olarak;

  • Her underlying değerleri int tipindedir.
  • Sabitler 0, 1, 2, 3.. enum üyelerinin tanımlama sıralarına göre otomatik olarak atanır.

Aşağıdaki örnek, sbyte tipinde bir underlying type ile Hiza adından bir enum tipini tanımlar;

enum Hiza : sbyte
 {
    Sol = -1,
    Orta = 0,
    Sağ = 1
 } 

Bu örnekte de görüldüğü gibi, bir enum üyesi tanımlama aşamasında sabit bir değere atanabilir. Burada atadığımız her sabitin underlying type (sbyte) tipinin aralığı içerisinde bulunmak zorundadır. Eğer bir enum tipi tanımlamasında açıkça belirli bir değere atanmamışsa, ilk değeri belirtmek için 0 (sıfır) değeri verilir ve tanımlama sıralamasına göre 1 artarak ilerler.


int i = (int)Renk.Mavi; // int i = 2;
Renk r = (Renk)2; // Renk r = Renk.Mavi

Enum tipleri için 0 özel bir değere sahiptir. Bu değer bir enum tipine atandığında otomatik olarak bu int değeri bir enum tipine çevrilir. Örneğin;


Renk r = 0;
Console.WriteLine(r);

Şeklinde bir kod yazdığımızda bu kodun çıktısı “Kırmızı” olur. Bu atama işlemini Hiza enum’u ile denemiş olsaydık bu enum’da bulunan Orta değeri 0 değerine atandığı için bu sefer ilk elemanı değil Orta değerini ekrana yazdırmış olacaktır. Fakat bu atama ve çevrilme işlemleri sadece 0 değeri için geçerlidir. Bu yüzden aşağıdaki kodumuz “Cannot convert type int to Renk” şeklinde bir hata verecektir.


Renk r = 1;
Console.WriteLine(r);

0’a enumlar tarafından bu şekilde davranmalarının iki nedeni vardır:

  • Bir enum’un ilk üyesi sıklıkla enum’un default value’su olarak kullanılır.
  • Birleşik enum tipleri için 0’ın anlamı “Flag Yok”

Ayrıca bu çevirme işlemlerini boolean tipler için de kullanabiliriz:


int i = (int)Hiza.Sol;
Hiza h = (Hiza) i;
bool Solmu = (int) h == -1;
Console.WriteLine(Solmu);

Burada da öncelikle Hiza enumunda yer alan Sol üyesinin üye sıralamasındaki yeri alınır. (0 numaralı üye) Daha sonra bu sıralama değeri Hiza tipinde hangi değişkene denk geldiği belirlenir. (0. üye Sol adlı üye) Ardında da bu üyenin değerinin –1 olup olmadığı (int)h ile belirlenip bool değişkenine atanır.

Enum’ları başka bir enum tipine açıkça atayabiliriz. Örneğin; YatayHiza adında bir enum oluşturalım:


enum YatayHiza
 {
Sol = Hiza.Sol,
Sağ = Hiza.Sağ,
Orta
 }

Bu tanımlamadan sonra aşağıdaki şekilde tanımlamalar yapabiliriz:


YatayHiza y = (YatayHiza)Hiza.Sağ;
YatayHiza i = (YatayHiza)(int)Hiza.Sağ;

Burada y ve i değişkenlerini yazdırdığımızda ikisinde de “Sağ” sonucunu elde ederiz.

Flag Kullanımı

Enum üyelerini birleştirebiliriz. Karışıklığı önlemek için, enum üyelerini değerleri ile birleştiririz. Örneğin aşağıdaki enum tanımımızı yapalım:


YatayHiza y = (YatayHiza)Hiza.Sağ;
YatayHiza i = (YatayHiza)(int)Hiza.Sağ;

Enum üyelerini birleştirmek için | ve & gibi bir operatörlerini kullanırız. Bu yüzden bu üyelerin değerlerini 2’nin üsleri şeklinde tanımladık:


[Flags]
 enum Kenar
 {
Sol = 1,
Sağ = 2,
Üst = 4,
Alt = 8
 }

| operatörü ile SağSol enum değişkenini oluşturalım. SağSol ve Sol üyelerini & operatörü ile and’lersek ortak payda Sol olduğundan altındaki ifadeyi yazdırır. formatlı değişkeni “Sol, Sağ” olarak tanımlanır Kenar tipindeki enum’u string’e çevirdiğimizde. k değişkenini de farklı yolla SağSol değişkenine eşitledik. Son olarak ta k değişkenini Sağ değişkeni ile ^ (XOR) işlemine soktuğumuzda geriye sadece Sol kalır.

Kural ile, Flags niteliği bir enum’un üyeleri birleştiriliyorsa her zaman uygulanmalıdır o enum yapısına. Eğer Flags niteliği kullanmadan bir enum oluşturur ve yine bu üyeleri birleştirmek isterseniz sorun çıkmaz. Yani yukarıdaki enum tanımlamasında [Flags] kısmını silsek bile bu programımız sorunsuz çalışır. Fakat, bir enum örneği için ToString() metodunu çağırmak bir string yerine bize bir numara getirecektir.

Kural ile, birleştirilmiş enum tipi tekil değil de çoğul tipte isimlerden oluşmalıdır.

Ayrıca enum üyelerini enum içerisinde tanımlama aşamasında da birleştirebiliriz:


[Flags]
 enum Kenar
 {
 Sol = 1,
 Sağ = 2,
 Üst = 4,
 Alt = 8,
 SolSağ = Sol | Sağ,
 AltÜst = Alt | Üst,
 Tüm = SolSağ | AltÜst
 }

Enumlarla ilgili işlem yapabileceğimiz operatörler aşağıdaki şekildedir;

= == != < > <= >= + – ^ & | ˜
+= -= ++ – sizeof

Bit bir, aritmetik ve karşılaştırma operatörleri geriye underlying değerlerinin işlemler sonuçlarını geri döndürür.

Type-Safe Konusu

Aşağıdaki enum’ yapısını göz önünde bulunduralım;


enum Kenar
 {
Sol,
Sağ,
Üst,
Alt
 }

Bir enum kendi underlying type versine atanabildiği gibi dışarıdan gerçek bir değer de enum’a bound edilebilir. Örneğin;


Kenar k = (Kenar)12345;
Console.WriteLine(k);

kodu bize 12345 çıktısını verir. Fakat bit ve aritmetik operatörler benzer bir şekilde geçersiz bir değer üretirler. Bu yüzden;


Kenar k = Kenar.Sağ;

k++;

kodu herhangi bir hata oluşturmaz.

Bir enum değerinin geçerli olup olmadığı kontrol etmek için Enum.IsDefined statik metodunu kullanabiliriz. Örneğin;


Kenar kenar = (Kenar)1;
 Console.WriteLine(Enum.IsDefined(typeof(Kenar), kenar));
 Kenar kenar1 = (Kenar)12345;
 Console.WriteLine(Enum.IsDefined(typeof(Kenar), kenar1));
 Kenar kenar2 = Kenar.Sol;
 Console.WriteLine(Enum.IsDefined(typeof(Kenar), kenar2));

kodumuzun çıktısı sırayla “True, False, True” olur. İlk satırımızın neden True döndürdüğünü yine bu konu içerisinde anlatmıştık.

Ne yazık ki, Enum.IsDefined metodu flag kullanılmış enum yapılarında kullanılamaz. Fakat aşağıdaki gibi tanımladığımız FlagTanımlandımı adında bir metod ile bir enum yapısının flag kullanılıp kullanılmadığını kontrol edebiliriz:


[Flags]
 enum Kenar
 {
 Sol = 1,
 Sağ = 2,
 Üst = 4,
 Alt = 8
 }

class Test
 {
 static bool FlagTanımlandımı(Enum e)
 {
 decimal d;
 return !decimal.TryParse(e.ToString(), out d);
 }

static void Main()
 {
 for (int i = 0; i <= 16; i++)
 {
 Kenar kenar = (Kenar)i;
 Console.WriteLine(FlagTanımlandımı(kenar) + " " + kenar);
 }
 }
 }

C# Delegate Tipine Genel Bir Bakış

Bir delegate tipi, özel parametre listesi ve geri dönüş tipi ile birlikte bir metoda referans bildirir. Böylece bir delegate tipi oluşturursak bir metoda referans içeren bir nesne yaratmış oluyoruz. Bu metod ise bu referans ile çağırılabilir konuma gelmiş oluyor.

Aşağıdaki örneğimiz bir Fonksiyon adında bir delegate tanımlar ve onu kullanır;

using System;

namespace ConsoleApplication1
{
 delegate double Fonksiyon (double d);

class Carpma
 {
 double carpim;

public Carpma(double carpim)
 {
 this.carpim = carpim;
 }

public double Carp(double x)
 {
 return x * carpim;
 }
 }

class Test
 {
 static double Kare(double x)
 {
 return x * x;
 }

static double[] Uygula(double[] a, Fonksiyon f)
 {
 double[] sonuc = new double[a.Length];

for (int i = 0; i < a.Length; i++)
 sonuc[i] = f(a[i]);

return sonuc;
 }

static void Main()
 {
 double[] a = { 0.0, 0.5, 1.0 };

double[] kareler = Uygula(a, Kare);

double[] sinusler = Uygula(a, Math.Sin);

Carpma c = new Carpma(2.0);
 double[] ciftler = Uygula(a, c.Carp);
 }
 }

}

Fonksiyon delegate tipinin örneği double tipinde argüman alan ve geriye double türünde bir veri döndüren herhangi bir metoda referans olabilir. Uygula() metodu verilen Fonksiyon’u double[] ile uygular ve geriye bir double[] sonucunu döndürür. Main metodu içerisinde, Uygula 3 farklı fonksiyonu double[] ile uygular.

Bir delegate, bir statik metoda (Kare ve Math.Sin metodları gibi) ya da bir metod örneğine (c.Carp metodu gibi)referans olabilir.

Bir metod örneğine referans olan bir delegate, belirli bir nesne örneğine de referans olabilir. Ve bu metod örneği delegate ile çağırıldığında, bu nesne this ile birlikte yönelir.

Bir delegate örneği yaratmak için, bir metodu delegate değişkenine atayabiliriz:

using System;

namespace ConsoleApplication1
{
 delegate int Donusturme (int x);

class Test
 {
 static int Kare (int x)
 {
 return x * x;
 }

static void Main()
 {
 Donusturme d = Kare; //Delegate örneği oluşturma
 int sonuc = d(3); //Delegate'i çağır
 Console.WriteLine(sonuc); //9
 }
 }

}

Görüldüğü üzere, bir delegate örneği çağırmak, tıpkı bir metod çağırımına benzer. (d(3) gibi) Bu arada;

 Donusturme d = Kare; 

satırı aşağıdaki kodun kısaltılmış halidir;

Donusturme d = new Donusturme (Kare);</p>  <p>d(3)

kodumuz da

d.Invoke(3)

kodunun kısaltılmışıdır.

Delegate Uyumlu Metodlar Yazmak

Bir delegate değişkeni metodlara dimanik olarak atanır. Bu delegate uyumlu metod yazmanın anahtarıdır. Aşağıdaki örnekte, Donustur adından bir yardımcı metodumuz var ve bir dizinin elemanlarını karelerine dönüştürür:

using System;

namespace ConsoleApplication1
{
 delegate int Donusturme (int x);

class Yardımcı
 {
 public static void Donustur(int[] degerler, Donusturme d)
 {
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = d(degerler[i]);
 }
 }

 class Test
 {
 static int Kare (int x)
 {
 return x * x;
 }

static void Main()
 {
 int[] degerler = { 1, 2, 3 };
 Yardımcı.Donustur(degerler, Kare);

foreach (int i in degerler)
 Console.Write(i + " "); // 1 4 9
 }
 }
}

Her delegate örneği çoklu kullanım yeteneğine sahiptir. Bunun anlamı; bir delegate örneği sadece tek bir metoda referans değil, birden fazla metoda referans olabilir. + ve += operatörleri delegate örneklerini birleştirir. Örneğin:

 DelegateOrnek d = MetodOrnek;</p>  <p>d += FarklıMetodOrnek; 

Bu durumda d delegate’ini çağırmak bu iki metodu çağırmak anlamına gelir. Eklenilme sırasına göre delegate tipleri çağırılır.

– ve –= operatörleri de sağıdaki metodu solundaki delegate örneğinden silerler. Örneğin;

 d –= MetodOrnek; 

Bu durumda d delegate’ini çağırmak sadece FarklıMetodOrnek metodunu çağırmamız anlamına gelir.

NOT: Delegate tipleri immutable tiplerdir. Bunun anlamı += veya –= operatörlerini kullandığınızda yeni bir delegate örneği oluşturmuş olursunuz ve varolan değişkeni ona atarsınız.

Çoklu gönderilen bir delegate tipi eğer void olmayan bir geri dönüş tipine sahipse, çağıran bu delegate’in içerdiği son metodun geri dönüş değerini alır. Önde bulunan metodlar yine de çağırılır fakat bunların geri dönüş değerleri görmezden gelinir.

NOT: Tüm delegate tipleri dolaylı olarak System.MulticastDelegate sınıfından türetilir. O da System.Delegate sınıfından kalıtım alır. += ve –= operatörlerini kullandığımızda aslında System.Delegate sınıfının statik tanımlanmış Combine ve Remove metodlarını kullanmış oluruz.

Statik Metod vs Metod Örneği

Bir delegate nesnesi bir metod örneğine atandığında, sadece atandığı metod olan referansı değil, ayrıca ait olduğu metodu da korur. System.Delegate sınıfının Target özelliği bu örneği gösterir. Örneğin;

using System;

namespace ConsoleApplication1
{
 public delegate void İlerleme (int TamamlanmaYuzdesi);

class Test
 {
 static void Main()
 {
 Ornek o = new Ornek();
 İlerleme i = o.İlerlemeOrnek;
 i(90); // 90
 Console.WriteLine(i.Target == o); // True
 Console.WriteLine(i.Method); // Void İlerlemeOrnek(Int32)
 }
 }

class Ornek
 {
 public void İlerlemeOrnek(int TamamlanmaYuzdesi)
 {
 Console.WriteLine(TamamlanmaYuzdesi);
 }
 }
}

Generic Delegate Tipleri

Bir delegate tipi generic tipinde parametreler içerebilir. Örneğin;

public delegate T Donusturme<T> (T arg);

Bu tanımlama ile, Donustur yardımcı metodunu herhangi bir tip ile kullanabiliriz:

 public class Yardımcı
 {
 public static void Donustur<T> (T[] degerler, Donusturme<T> t)
 {
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = t(degerler[i]);
 }
 } 

Func ve Action Delegate’leri

Generic delegate’ler ile, herhangi bir geri dönüş tipi ve herhangi sayıda argümanı olan metodlar için küçük delegate tipleri oluşturmak mümkündür. Bu delegate tiplerinden olan Func ve Action delegate’leri System namespace’i içerisinde yer alır.

 delegate Sonuc func<out Sonuc>();
 delegate Sonuc func<in T, out Sonuc>(T arg);

delegate void Action ();
 delegate void Action<in T>(T arg); 

Bir önceki örneğimizde Donustur delegate örneğini, sadece basit bir T argümanı alan ve aynı cinsten bir değer geri döndüren Func delegate’i ile yer değiştirebiliriz:

 public static void Donustur<T> (T[] degerler, Func <T, T> donus)
{
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = donus (degerler[i]);
} 

Delegate  Uyumluluğu

Tip

Tüm delegate tipleri, imzaları aynı olsa bile birbiri ile uyuşmayan tiplerdir.

 delegate void D1();
 delegate void D2();

D1 d1 = Metod1;
 D2 d2 = d1; // Derleme zamanı hatası 

Aşağıdaki örnek ise herhangi bir hata vermez:

 D2 d2 = new D2 (d1); 

Delegate örnekleri eğer aynı metodlara hedef gösterilirse bunlar eşit olarak nitelendirilir. Örneğin:

 delegate void D();
 ...
 D d1 = Metod1;
 D d2 = Metod1;
 Console.WriteLine (d1 == d2); // True döndürür. 

Birden fazla metoda referans gösteren delegate tipleri, eğer aynı metodlar aynı sırayla ise bu tipler de eşit olarak nitelendirir.

Parametre

Bir delegate tipi, hedef gösterdiği metodtan daha belirli parametre tiplerine sahip olabilir. Bu duruma contravariance adı verilir. Örneğin;

 using System;

namespace ConsoleApplication1
{
 delegate void StringEtki (string s);

class Test
 {
 static void Main()
 {
 StringEtki se = new StringEtki(NesneEtki);
 se("Merhaba");
 }
 static void NesneEtki(object o)
 {
 Console.WriteLine(o);
 }
 }
} 

Bu örneğimizde, StringEtki delegate tipi string tipindeki bir argüman ile çağırılır. Ve bu argüman hedef metoda aktarıldığında, bu argüman dolaylı olarak object tipine çevrilir (üst tipi olduğundan).

Geri Dönüş

Delegate’in geri dönüş tipi hedeflediği metodun geri dönüş tipinden daha az belirli olabilir. Bu duruma da covariance adı verilir. Örneğin;

 using System;

namespace ConsoleApplication1
{
 delegate object NesneGetir ();

class Test
 {
 static string StringGetir()
 {
 return "Merhaba";
 }
 static void Main()
 {
 NesneGetir o = new NesneGetir(StringGetir);
 object sonuc = o();
 Console.WriteLine(sonuc); //Merhaba
 }
 }
} 

NesneGetir geriye bir object döndürmesi bekler, fakat object alt sınıfı bunu ayrıca yapar. Delegate’in geri dönüş tipi “covariant”’tir.

C#’ta Dizi Yapısı

C#’ta bir dizi, doğrudan ulaşılabilen sayısı belli üyeleri içeren bir veri yapısıdır. Dizinin içindeki elemanlar, dizi elemanları, hepsi aynı tipte ve bu tip dizi elemanlarının tipi olarak adlandırılır.

Dizi tipleri birer referans tipidir ve dizi elemanlarının tanımlanması bu dizi örneğine referans içerir. Gerçek dizi örneği dinamik olarak runtime zamanında new operatörü kullanılarak elde edilir. New operatörü kullanılarak dinamik olarak dizilere bellek ayrılır. New operatörü otomatik olarak dizi elamanlarının default değerlerini ilk kullanıma hazırlar. Örneğin; tüm sayısal tipler için 0, tüm referans tipler için ise null.

Örneğin aşağıdaki örnek, bir int dizisi yaratıp, ilk kullanıma hazırlayıp, sonra da çıktılarını gösteren bir örnektir;

using System;

class Test 
{ 
    static void Main() 
    { 
        int[] dizi = new int[10]; 
        for (int i = 0; i < dizi.Length; i++ ) 
        { 
            dizi[i] = i * i; 
        } 
        for (int j = 0; j < dizi.Length; j++ ) 
        { 
            Console.WriteLine("dizi[{0}] = {1}", j, dizi[j]); 
        } 
    } 
}

Buradaki örnekte tek bir boyutlu dizi ile çalıştık. C# birden fazla boyutlu dizi kullanımına izin vermektedir. Bir dizinin boyut numarası aynı zamanda o dizi tipinin rank’ı olarak ta bilinir. Bir dizinin boyut sayısını dizi tanımında kullanılan dizinin içindeki virgül sayısını bir arttırarak bulabiliriz. Aşağıdaki örnekler bir, iki ve üç boyutlu dizilere örneklerdir;

int[] dizi1 = new int[10]; 
int[,] dizi2 = new int[10, 5]; 
int[, ,] dizi3 = new int[10, 5, 2];

dizi1 dizisi 10 elemana, dizi2 dizisi 50 (10 * 5) elemana ve dizi3 dizisi de 100 (10 * 5 * 2) elemana sahiptirler.

Bir dizinin eleman tipi herhangi bir tip olabilir hatta dizi tipinde bile olabilir. Dizi tipindeki elemanlara sahip dizilere Jagged array adı verilir çünkü bu dizinin elemanlarının uzunlukları aynı olmak zorunda değildir. Aşağıdaki örnek int dizi tipi içeren elementlere sahip olan bir diziyi ifade eder.

int[ ][ ] dizi = new int[3][ ]; 
dizi[0] = new int[10]; 
dizi[1] = new int[7]; 
dizi[2] = new int[20];

array-1

Yukarıdaki ilk satır, 3 elemanlı bir dizi oluşturur, bu elemanların hepsi int[] tipinde ve hepsinin varsayılan değeri null’dır. Altındaki satırlar, bu int[] tipindeki dizi elemanlarının uzunluklarını belirlerler.

Unutmayın ki new operatörü { } sınır belirleyiciler içinde dizinin elemanlarına ilk oluşturulma anında değer atamaya imkan verir.

int[ ] dizi = new int[ ] { 1, 2, 3 };

Hatta bu kullanımı aşağıdaki şekilde kısaltabiliriz de.

int[] dizi = { 1, 2, 3 };

Bu iki kullanım da aşağıdaki gösterilen adımlara eşittir:

int[ ] t = new int[3]; 
t[0] = 1; 
t[1] = 2; 
t[2] = 3; 
int[ ] dizi = t;

Dizilerde referans atama konusunda tıpkı nesneler gibi, bir diziyi diğer diziye atadığınızda sadece değişkenin ilişkili olduğu nesneyi değiştiriyorsunuz. Örneğin aşağıdaki programda, dizi tanımlamaları yapılan dizi1 ve dizi2 dizileri, atama yapıldıktan sonra her iki dizi referans değişkenleri de aynı nesneyi gösterdiğinden dizi değerleri aynı olur.

using System;

class Test 
{ 
    static void Main() 
    { 
        int[] dizi1 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
        int[] dizi2 = { 0, -1, -2, -3, -4, -5, -6, -7, -8, -9 };

        dizi2 = dizi1;

        foreach(var i in dizi1) 
        { 
            Console.Write(dizi1[i]); 
        }

        Console.WriteLine();

        foreach (var i in dizi2) 
        { 
            Console.Write(dizi2[i]); 
        } 
    } 
}

Çıktı:

0123456789

0123456789 Press any key to continue . . .

C#’ta Struct Yapısı

Sınıflar gibi, struct yapıları veri ve fonksiyon üyeleri içeren veri yapılarıdır. Sınıflardan farklı olarak, struct yapısı değer tipidir ve heap bölge tahsisi gerektirmez. Bir struct değişkeni, direkt olarak struct’ın verisini tutar. Oysa sınıf tipinde bir değişken, dinamik olarak ayrılmış nesneye referans tutar. Struct yapısı, kullanıcı tanımlı kalıtımı desteklemez ve tüm struct yapıları dolaylı olarak object tipinden kalıtım alır.

Önemli: Struct yapılarının heap bölge tahisisi gerektirmemesi, bu yapıların hiçbir zaman bunu yapmadıkları anlamına gelmez.

Bu yapılar özellikle küçük veri yapıları kullanımında kullanışlı olurlar. Karmaşık sayılar ve koordinat sisteminde noktalar struct yapılarına iyi örneklerdir. Bir yapıda sınıf yerine struct kullanımı, bellek tahsis etmede ve programınızın performansında büyük farklılıklar yaratabilir.

Örneğin; aşağıdaki program 100 nokta oluşturan bir programdır. Sınıf yapısı ile kullandığımızda 101 adet farklı nesne – 1 adet dizi için, 100 adet te dizi üyeleri için – oluşturulur:

using System;


class Nokta

{

    public int x, y;


    public Nokta(int x, int y)

    {

        this.x = x;

        this.y = y;

    }

}

class Test

{

    static void Main()

    {

        Nokta[] noktalar = new Nokta[100];

        for (int i = 0; i < 100; i++)

            noktalar[i] = new Nokta(i, i);

    }

}

struct-1

Alternatif olarak Nokta yapısını struct yapabiliriz.

struct Nokta

{

    public int x, y;


    public Nokta(int x, int y)

    {

        this.x = x;

        this.y = y;

    }

}

Burada sadece 1 nesne yaratılır – 1 adet dizi için – ve Nokta örnekleri dizi içerisinde saklanırlar.

struct-2

Önemli: Struct yapısının performans açısından sağladığı faydayı “Her zaman struct kullanın” şeklinde algılamak yanlış olur. Tabi ki bazı senaryolarda struct yapısına bellek ayırmak ve bellekten almak daha az zaman alır fakat her struct ataması bilindiği gibi değer kopyalamasıdır (value copy). Bu her zaman referans kopyalamasından daha fazla zaman alır.

Struct yapıcıları new operatörü ile çağrılırlar fakat bu demek değildir ki belirli bir bellek ayrılmıştır. Nesnenin veya referansının dinamik olarak bellek ayrımı yerine, bir struct yapıcısı basitçe struct değerinin kendisini döndürür (genellikle yığının geçici bölgesinde) ve bu değer gereli olduğunda kopyalanır.

Sınıflarda, iki farklı değişken aynı nesneye referans gösterebilir ve bu şekilde bir değer üzerindeki işlemler aynı nesneyi referans eden diğer değişkeni etkileyebilir. Struct yapılarında, her değişken kendi verisinin kopyasını tutar ve bir değişkenin diğerini etkilemesi imkansızdır. Örneğin aşağıdaki kodu inceleyelim;

Nokta a = new Nokta(10, 10);

Nokta b = a;

a.x = 20;

Console.WriteLine(b.x);

Eğer buradaki Nokta örneği bir sınıf (class) ise, sonucumuz 20 olur çünkü a ve b aynı nesneye referans gösterirler. Eğer Nokta örneği bir struct ise, sonucumuz 10 olur çünkü ikinci satırdaki atama işlemi değer olarak a’daki değerleri b’ye kopyalar ve bu kopyalama 3. satırdaki a.x atamasını etkilemez.

Bu örnekte struct kullanımının 2 noktasının farkına vardır. Birincisi, tüm struct yapısını kopyalamak bir nesne referansını kopyalamaktan daha az verimli. Böylece atama yapma ve değer parametresi geçirme struct yapılarında referans tiplerine göre daha maliyetli olabilir. İkincisi, ref ve out parametreleri dışında, struct yapısına referans oluşturma imkansızdır.

Bill Wagner: Eğer tüm durumlarda değer anlamı (value semantics) istemiyorsanız, sınıf kullanmalısınız. Sınıflar bazı durumlarda değer anlamı uygulayabilirler, (string gibi) ama default olarak referans anlamına uyarlar. Bu fark sizin tasarımınızda yığın vs hesap bellek ayrımına göre daha çok farklılık gösterir.

.NET içerisinde Değer Tipi ve Referans Tipi

.NET kodunun çalıştırılmasını sağlayan CLR yapısı iki çeşit veri tipini destekler; referans tipi (reference types) ve değer tipi (value types). FCL (Framework Class Library) içerisinde bulunan tiplerin çoğu referans tipi olsa da, programcıların kullandığı tiplerin çoğunu değer tipleri oluşturur. Referans tiplerine her zaman belleğin “Heap” kısmından yer ayrılır ve C#’ın new operatörü de bu nesnenin bellek adresini döndürür (Bellek adresi de nesnenin bitlerini kaynak gösterir). Şunu aklınızda tutmalısınız ki, referans tipleri ile çalışırken bazı performans özelliklerine dikkat etmelisiniz. Şu 4 maddeyi göz önünde bulundurun:

1. Yönetilen heap kısmından bellek ayrılmak zorundadır.

2.Heap bölgesine ayrılmış her nesnenin kendisi ile ilişkili ilave üyeleri mevcuttur.

3.Nesnenin içineki diğer bitler her zaman sınıfa ayarlanır. (Alanlar için)

4.Bir nesneye heap bölgesinden kaynak ayırabilmek bazen Garbage Collection’ı ortaya çıkartabilir.

Her tip referans tipi olsaydı, bir uygulamanın performansı fazlasıyla kayba uğrardı. Düşünün ki, her Int32 tipini kullandığınızda bellekten bir yer tahsis etmek zorundasınız. Ne kadar kötü bir performans alacağınızı farketmişsinizdir. Performans arttırmak için, sıkça tip kullanılan uygulamalarda, CLR “hafif siklet” olarak adlandırdığı değer tiplerini önerir. Değer tipi örnekleri için çoğunlukla belleğin stack bölgesinden yer tahsis edilir (Ayrıca bir referans tipi nesnenin içerisine bir alan olarak gömülebilirler). Bu tipin bir değişkeni, örneğine bir pointer içermez, bu değişken örneğinin kendisinin bir alanını içerir. Değer tipi örnekleri, garbage collector’ın kontrolü altına girmezler ve bu sayede yönetilen heap kısmının üzerindeki yükü azaltır.

.NET Framework SDK dökümanı hangi tiplerin değer tipi, hangi tiplerin referans tipi olduğunu açık bir şekilde gösterir. Bu dökümana baktığımızda sınıf tipleri referans tipleridir. Örneğin; System.Exception, System.IO.FileStream ve System.Random birer referans tipleridir. Diğer taraftan döküman structure (yapı) ve enum tiplerinin değer tipi olduğunu gösterir. Örneğin; System.Int32 yapısı, System.DayOfWeek enum tipi, System.Decimal yapıları birer değer tipidir.

Biraz daha derinlere inmeye çalışırsak, tüm structure yapıların System.ValueType abstract tipinden türediğini görürüz. System.ValueType tipinin de System.Object tipinden türetildiğini görürürz. Tüm enum tipleri System.Enum abstract tipinden türetilmişlerdir ki bu tip te yine System.ValueType tipinden türetilir. CLR ve diğer tüm programlama dilleri enum tiplerine ayrı bir davranış sergilerler. Burada kafanıza şu soru takılabilir. Görüldüğü üzere System.ValueType bir sınıf tipidir ve sınıf tipleri referans tipidir. O halde Nasıl bir değer tipi bir referans tipinden türeyebilir?

Kendi değer tipinizi tanımlamaya çalışırken değer tiplerini temel tip olarak kullanamazsınız. Çünkü tüm değer tipleri sealed (yani o sınıf için türetilme işlemini önler) olarak tanımlıdır. Bu nedenle yeni oluşturmak istediğini referans tipinde ya da değer tipinde bir tip için, temel tip olarak değer tiplerini kullanamazsınız. Bir örnekle açıklamaya çalışırsam; Boolean, Char, Int32, Uint64, Single, Double tiplerini temel tip şeklinde kullanıp yeni bir tip oluşturmak imkansızdır.

Aşağıdaki örnek size referans tipi ve değer tiplerinin farklılığını açıklar:

using System;
namespace Program
{
 //Referans Tipi [Sınıf olduğundan]
 class ReferansTipi
 {
 public int x;
 }

//Değer Tipi [Struct olduğundan]
 struct DegerTipi
 {
 public int x;
 }

public class Program
 {
 public static void Main(string[] args)
 {
 ReferansTipi r1 = new ReferansTipi(); // Yer Tahsisi - Heap
 DegerTipi d1 = new DegerTipi(); // Yer Tahsisi - Stack

r1.x = 5; // Pointer referansı
 d1.x = 5; // Stack içerisinde değişim

Console.WriteLine(r1.x); // 5
 Console.WriteLine(d1.x); // 5
 }
 }
}

Bu kod parçasını çalıştırdığımızda bilgisayarımızın bellek kısmında aşağıdaki resimde görülen bir durum oluşur:

stack-heap

Harika(!) bir çizer olduğumu da bu resimden anlamışsınızdır. Şimdi aşağıdaki kodları yukarıda belirttiğimiz kodun içerisindeki Main() metodunun sonuna ekleyelim ve bellek kısmında nasıl bir değişiklik olduğuna bakalım:

ReferansTipi r2 = r1; // Sadece referansı (pointer) kopyalama

DegerTipi d2 = d1; // Yer Tahsisi - Stack ve üyeyi kopyalama

r1.x = 6; // r1.x ve r2.x değerlerini değiştirir.
d1.x = 7; // d1.x'i değiştirir, d2.x'i değiştirmez.

Console.WriteLine(r1.x); //6
Console.WriteLine(r2.x); //6
Console.WriteLine(d1.x); //7
Console.WriteLine(d2.x); //5

stack-heap1

Bu kod içerisinde bazı satırlara değinmek istiyorum:

DegerTipi d1 = new DegerTipi(); // Yer Tahsisi – Stack

Bu koda direkt olarak baktığımızda belleğin heap bölgesinden yer tahsis edildiği bilgisine varılır. Fakat, C# DegerTipi örneğinin bir değer tipi olduğunu bilir ve bununiçin stack bölgesinden yer ayıracak kodu üretir. C# ayrıca bu değer tipinin içerisindeki tüm alanların sıfıra set edildiğine emin olur. Aynı satır kod şu şekilde de yazılabilir;

DegerTipi d1;

Bu kod aynı şekilde thread kısmındaki stack bölgesinden yer ayırır ve tüm alanları sıfıraatar. Bu iki kod arasındaki tek fark, eğer new operatörünü kullanırsak C# düşünür ki bu örnek ilk kullanıma hazırdır. Hemen bu konuyu daha da açık hale getirelim;

DegerTipi d3 = new DegerTipi(); // Bu iki satır derlenir çünkü
int a = d3.x; // C#, d3'ün alanlarını sıfıra atandığını bilir.

DegerTipi d3; // Bu iki satır derlenimez çünkü C#, d3'ün alanlarını sıfıra
int a = d3.x; // atanmadığı düşünür. Use of possibly unassigned field 'd3' hatası alırız.

Eğer .NET içerisinde kendi değişken tipinizi tasarlamak isterseniz, bu tipin referans tipi mi yoksa değer tipi mi olacağına dikkatle karar vermelisiniz. Bazı durumlarda değer tipleri daha iyi performans verebilir. Eğer aşağıdaki 3 özellikte tasarlamak istediğiniz tip için uygunsa bu tipi değer tipi olarak tasarlamalısınız;

  1. Tip ilkel bir tip olursa (primitive types). Burada bahsetmek istediğim bu tipin içerisindeki alanlarda herhangi bir düzenlemesi gereken üyesi yoksa mantığıdır.
  2. Bu tipin diğer tiplerden kalıtım almasına gerek yoksa.
  3. Bu tipten türetilecek başka tipler yoksa.
    Yeni değişken tipinizin örnek boyutu da burada hesaba katılmalıdır. Çünkü varsayılan olarak, argümanlar metodlara değer ile geçirilirler (pass by value). Böylece değer tipi örneğinin içindeki alan kopyalanır ki bu da performansı azaltır. Tekrardan, geriye bir değer tipi döndüren metodlar, metodu çağıran tarafından bellekte örneğin içindeki alananın kopyası için yer tahsisi yaparlar ki bu da performansı azaltır.

Değer tiplerinin temel avantajı, bir nesne olarak belleğin yönetimli heap kısmında yer kaplamazlar. Tabi ki değer tiplerinin, referans tiplerine göre birkaç sınırlaması mevcuttur. Şimdi bu durumlara bir göz atalım;

1. Değer tipi nesnelerinin iki farklı gösterimi vardır. Boxed ve unboxed. Referans tipleri her zaman boxed formdadırlar.

2. Değer tipleri System.ValueType sınıfından türetilmişlerdir. Bu tip System.Object’te tanımlanan aynı metodları sağlar. Fakat, System.ValueType Equals() metodunu override eder. Böylece iki nesnedeki alanların içindeki değerler eşleşirse geriye true döndürür. Ek olarak, System.ValueType GetHashCode() metodunu da override eder ki bu method bir değer tipi için algoritma kullanarak bir hash kodu üretir.

3.Değer tiplerini temel sınıf (base class) olarak, herhangi bir değer tipi veya referans tipi üretemeyeceğimiz için, bir değer tipine yeni bir sanal (virtual) metod tanımlamamalıyız. Hiçbir metod soyut (abstract) olmamalıdır.

4. Referans tipi değişkenler belleğin heap kısmındaki nesnenin bellek adresini içerirler. Default olarak, referans tipinde bir değişken oluşturulduğunda, null’a atanır ki bunun anlamı bu değişken henüz geçerli bir nesneyi göstermiyordur. Null olarak atanmış bir referans nesnesini kullanmaya çalıştığımızda NullReferanceException hatası alırız. Buna karşı, değer tipi değişkenleri her zaman tanımlandığı tipin değerini içerir ve bu değer tipinin tüm üyeleri 0’a atanır. Değer tipi değişkeni bir referans içermediği için, bir değer tipine ulaşırken NullReferanceException hatası almamız imkansızdır.

5.Bir değer tipi değişkenini başka bir değer tipi değişkenine atadığımızda, alandan alana kopyalama gerçekleşir. Bir referans tipi değişkenini başka bir referans tipi değişkenine atadığımızda sadece bellek adresi kopyalanır.

6.Bir önceki maddeden dolayı, referans tipleri heap bölgesinde aynı nesneye referans olabileceklerinden, bir referans tipindeki değişkenin üzerindeki değişiklik diğer bir referans tipindeki değişkeni değiştirebilir. Diğer taraftan, bir değer tipindeki değişkenin diğer değer tipindeki değişkeni etkilemesi imkansızdır.

Değer tiplerinde Equals() metodunun varsayılan uygulaması olarak bit bit karşılaştırma yapması bazı durumlarda performansı yükseltebilir. Eğer iki adet değer tipi değişkeninizin içerisi pozitif sıfır ve negatif sıfır ise, sırasıyla eşit değil olarak karşılaştırılırlar. Ama Equals() metodunu varsayılan davranışı için override edebilirsiniz. Örneğin;

using System;

namespace Program
{
    public class Program
    {
        struct S
        {
            public double X;
        }

        public static void Main(string[] args)
        {
            var i = new S { X = 0.0 };
            var j = new S { X = -0.0 };

            Console.WriteLine(i.X.Equals(j.X)); // True
            Console.WriteLine(i.Equals(j)); // False
        }
    }
}

C#’ta Method Aşırı Yüklemek

C#’ta method aşırı yüklenmesi, method imzaları farklı olan aynı isimdeki methodları aynı sınıf içerisinde kullanıma izin verir. Aşırı yükleme mekanizması, parametrelere uygun eşleşen methodu bulur ya da herhangi bir basit eşleşme olmazsa bir hata üretir. Aşağıdaki örnek, arışı yükleme mekanizmasının nasıl çalıştığına bir örnektir. Main() methodunun içinde hangi methodun çağrılacağını yorum olarak yazdım.

using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
class Test
{
static void A()
{
Console.WriteLine("A()");
}
static void A(object x)
{
Console.WriteLine("A(object x)");
}
static void A(int x)
{
Console.WriteLine("A(int x)");
}
static void A(double x)
{
Console.WriteLine("A(double x)");
}
static void A<T>(T x)
{
Console.WriteLine("A<T>(T x)");
}
static void A(double x, double y)
{
Console.WriteLine("A(double x, double y)");
}
static void Main()
{
A();          // Çağırılan F()
A(1);         // Çağırılan F(int)
A(1.0);       // Çağırılan F(double)
A("abc");     // Çağırılan F(object)
A((double)1); // Çağırılan F(double)
A((object)1); // Çağırılan F(object)
A<int>(1);    // Çağırılan F<T>(T)
A(1, 1);      // Çağırılan F(double, double)
}
}
}

Önemli: Method aşırı yükleme yanlış kullanımlara neden olabilir. Genel olarak bakarsak, method yüklemeyi sadece methodların hepsi anlamsal olarak aynı işi yaptığı zamanda kullanmalıyız. Bir çok yazılımcı, bu olayı “tek bir method farklı argümanlara sahip” olabilir şeklinde düşünür. Aslında, yerel değişkenin, parametrenin veya özelliğin tipini değiştirme, farklı yüklemelerin çağırılmasına neden olabilir. Yazılımcılar method aşırı yüklemesinin yan etkisini göremeyebilirler. Fakat kullanıcılar için bu tamamen farklı sonuçlar doğurabilir.

Brad Abrams, .NET framework’ü geliştirme aşamasının başlarında, String adında bir sınıfın içinde şu method yüklemelerin problem yarattığını söylüyor:

public class String
{
public int IndexOf (string value);
// Örnekteki değerin index'ini geri döndürür.
public int IndexOf (char value);
// Örnekteki değerin index'ini geri döndürür.
public int IndexOf (char[] value);
// Değerin içindeki karakterlerin ilk index'ini geri döndürür.
} 

Buradaki son method, farklı sonuçlara yol açtığını söylüyor. Şöyle ki;

 "Joshua, Hannah, Joseph".IndexOf("Hannah"); // Geriye 7 döndürür. 

Fakat

"Joshua, Hannah, Joseph".IndexOf(new char [] {'H','a','n','n','a,'h;");
 // Geriye 3 döndürür.

Buradaki son methodun isminin method aşırı yüklemesine bağlı kalınarak aşağıdaki şekilde olması gerektiğini söyler:

public int IndexOfAny (char [] value);

C#’ta Virtual, Override ve Abstract Methodlar

C#’ta bir method örneği, virtual anahtar sözcüğü ile tanımlanırsa, o methoda virtual (sanal) method adı verilir.

Bir sanal method, ana sınıf tipinden ziyade, güncel atanmış tip ile ilişkilendirilmiş methodu kullanmanıza izin verir.

Bir sanal method türetilmiş sınıfı tarafından etkisiz hale getirilebilir. Bir method örneğinde override anahtar sözcüğü varsa, o method, kalıtım alınmış sanal methodu aynı method imzası ile geçersiz kılar. Bu tür bir sanal method eğer overload olmuşsa, tanımlanmış değişkenin data tipinden ziyade verinin gerçek sınıf tipi runtime zamanında kullanılır. Bunun anlamı; temel sınıf bir çok türetilmiş sınıf tarafından kullanılabilir.

Önemli: Buradaki ince bir nokta, override edilmiş bir sanal method, hala o sınıfın bir methodu olarak hesab katılır. Türetilmiş sınıf, sanal sınıf override edildiği zaman onu göstermek zorundadır. Bu override anahtar sözcüğünü yeni method oluşumunda kullanılarak gösterilir.

Bir abstract (soyut) method, uygulanmayan bir sanal methodtur. Bu methodlar, abstract anahtar sözcüğü ile tanımlanır ve sadece astract olarak tanımlanmış sınıflara izin verilir. Bir soyut sınıf, soyut olmayan türetilmiş tüm sınıflar tarafından override edilmelidir.

Aşağıdaki örnekte; soyut sınıf olarak tanımlanmış Deyim sınıfı, ki bir deyim ifade ağacı düğümü temsil eder, 3 tane de türetilmiş sınıf, Sabit, DegiskenReferansi ve İslem, ki bunlar da sabitlerin, değişken referanslarının ve aritmetik işlemlerin deyim ifade ağaçlarını uygularlar.


using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
public abstract class Deyim
{
public abstract double Hesapla(Hashtable vars);
}
public class Sabit : Deyim
{
double value;
public Sabit(double value)
{
this.value = value;
}
public override double Hesapla(Hashtable vars)
{
return value;
}
}
public class DegiskenReferansi : Deyim
{
string name;
public DegiskenReferansi(string name)
{
this.name = name;
}
public override double Hesapla(Hashtable vars)
{
object value = vars[name];
if (value == null)
{
throw new Exception("Bilinmeyen deşiken " + name);
}
return Convert.ToDouble(value);
}
}
public class İslem : Deyim
{
Deyim sol;
char op;
Deyim sag;

public İslem(Deyim sol, char op, Deyim sag)
{
this.sol = sol;
this.op = op;
this.sag = sag;
}

public override double Hesapla(Hashtable vars)
{
double x = sol.Hesapla(vars);
double y = sag.Hesapla(vars);
switch (op)
{
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Bilinmeyen operatör");
}
}
} 

Bu 4 sınıf, modern matematik işlemler için kullanılabilir. Örneğin, bu sınıf örneklerini kullanarak x + 7 deyimi şu şekilde gösterilebilir;


static void Main()
{
Deyim d = new İslem(new DegiskenReferansi("x"), '+', new Sabit(7));
} 

Burada Deyim sınıfının Hesapla methodu verilen değeri hesaplamak ve bir dobule değeri üretmek görevindedir. Bu method Hashtable adında değişken isimlerini ve değerlerini içeren bir parametre alır. Hesapla methodu, sanal soyut bir method olduğundan, bunun anlamı; soyut olmayan türetilmiş sınıflar güncel bir uygulama sağlamak için bu methodu override etmek zorundalar.

Sabit sınıfının Hesapla implementasyonu, basit olarak geriye depolanmış bir sabit (constant) döndürür. DegiskenReferansi implementasyonu, Hashtable içerisindeki değişken ismine bakar ve sonuçlanan değeri geri döndürür. İslem implementasyonun görevi, öncelikle sol ve sağ operandlarını (Recursif olarak çağırılan Hesapla() methodları ile) hesaplayarak, verilen aritmetik işlemi gerçekleştirmektir.

Aşağıdaki program Deyim sınıfını kullanarak x * (y + 3) işlemini farklı değerler kullanarak hesaplar;


static void Main()
{
Deyim d = new İslem(new DegiskenReferansi("x"), '*', new İslem(new DegiskenReferansi("y"), '+', new Sabit(3)));

Hashtable vars = new Hashtable();

vars["x"] = 2;
vars["y"] = 5;
Console.WriteLine(d.Hesapla(vars)); // Çıktı 16 olur.

vars["x"] = 3;
vars["y"] = 7;
Console.WriteLine(d.Hesapla(vars)); // Çıktı 30 olur.
}