.NET Framework Tip Temelleri
.NET, modern yazılım geliştirme dünyasında sıklıkla kullanılan popüler bir platformdur. Bu platform, .NET dilleri (C#, VB.NET, F# vb.) tarafından yazılan uygulamaları çalıştırmak için kullanılır. Bu uygulamalar bellek yönetiminde iki temel alanı kullanır: Heap ve Stack. Stack bellek, metot çağrıları, değişkenlerin tanımlanması ve işlevsel geçişler gibi yerel değişkenlerin saklanması için kullanılırken, Heap bellek, daha geniş ve karmaşık nesnelerin depolanması için kullanılır. Bu iki bellek türü, .NET platformunda önemli bir rol oynar ve bir yazılım geliştiricinin bunların ne olduklarını ve nasıl çalıştıklarını anlaması önemlidir.
Stack bellek, önceden belirlenmiş bir boyutu olan bellek bloklarını içerir ve bir program çalıştırıldığında otomatik olarak oluşturulur. Stack bellek, özellikle yerel değişkenlerin, metot çağrıları ve işlevsel geçişlerin saklanması gibi kısa ömürlü verilerin saklanması için kullanılır. Stack bellekteki veriler, son giren ilk çıkar (LIFO) prensibiyle yönetilir ve bellek blokları işlevlerin yürütülmesi sırasında atılabilir. Stack bellek hızlıdır, çünkü bellek blokları sabit bir boyuta sahiptir ve bellek erişimi hızlıdır. Stack bellek, geliştirici tarafından değil, compiler tarafından yönetilir.
Heap bellek ise, programın çalışma zamanında dinamik olarak oluşturulan ve atılabilen, değişken boyutlara sahip bellek bloklarını içerir. Heap bellek, genellikle daha uzun ömürlü verilerin (nesneler, diziler, sınıflar vb.) saklanması için kullanılır. Heap bellek, nesnelerin birbirine referans verdiği durumlarda kullanılır. Bu bellek türü, programın çalışması sırasında yönetilir ve bellek blokları işlevlerin yürütülmesi sırasında atılmaz. Heap bellek, stack belleğe göre daha yavaştır, çünkü bellek blokları dinamik olarak boyutlandırılır ve bellek erişimi daha yavaştır.
Tip Temelleri
CLR, her tipin nihai olarak System.Object tipinden türetilmesini gerektirir. Dolayısıyla .NET’te bütün tipler System.Object temel tipinden türer. Buda herhangi bir tipten oluşturulan objede garanti olarak aşağıdaki metotlar bulunur.
- Equals() : Objeleri karşılaştırmak için kullanılır. Eğer iki obje aynı değere sahipse true döner, değilse false döner.
- GetHashCode() : Objenin hash kod değerini döner. Eğer bir tip hash table koleksiyonu kullanıyorsa bu metotu override etmesi gerekir.
- ToString() : Bu metot bir tipten oluşturulan objenin adını(this.GetType().FullName) getirir.
- GetType() : GetType metotunu çağıran objenin tip nesne örneğini döndürür.
Aynı zamanda System.Object tipi içerisinde protected olarak tanımlanan metotları da miras alırlar. Bunlar;
- MemberwiseClone() : Bu virtual olmayan metot tipten yeni bir obje yaratıp onun referansını döner.
- Finalize() : Bu virtual olan metot nesne Garbage collector tarafından yok edildiğinde çağrılan son metottur.
CLR bütün objelerin new operatörü ile oluşturulmasını ister. Aşağıda new operatörünün ne yaptığı adım adım anlatılmaktadır.
- Bir tipten ve tipin miras aldığı tiplerden bir obje oluşturulması için gerekli olan bellek alanı miktarını hesaplar. CLR’ın objeyi yönetmesi için her objenin Heap üzerinde sahip olduğu object pointer, sync block index bilgisi vardır. Bu bilgilerin byte miktarlarını da objeye ekler.
- Bu obje ve miras aldığı tipler için bellekte yer ayırır.
- Objenin type pointer ve sync block index değerlerini başlatır.
- Tipin constructor metotunun parametrelerini geçip metotu çalıştırır. Derleyicilerin çoğu constructor çalıştırırken miras alınan tiplerin constructor’larını otomatik çalıştırır.
Bütün bu işlemlerden sonra new operatörü oluşturulan objenin referans değerini döner.
Tipleri Birbirine Dönüştürme
CLR’in en önemli özelliklerinden bir tanesi tip güvenli olmasıdır. Çalışma zamanında, CLR her zaman objenin hangi tipte olduğunu bilir. Bir objenin gerçek tipini GetType() metotu ile her zaman bilinebilir. Çünkü bu metot virtual değil. O yüzden tipler GetType() metotunu ezemez. Dolayısıyla bir türün başka bir türü taklit etmesi mümkün değildir.
Yazılımcılar bir tipi diğer tiplere çevirme ihtiyacı duyabildikleri için CLR bir objenin tipine veya miras aldığı tiplere çevrilmesine izin verir. Bu çevirme işlemi için seçilen dile göre kurallar değişmektedir. Örneğin, C# miras alınan tiplere(base) çevirme işleminde herhangi özel bir kural gerektirmez ve örtük(implicit) olarak çevirme işlemini gerçekleştirir. Fakat çevirme işlemi alt tiplere(derived) doğru yapılacaksa açık açık (explicit) hangi tipe çevrileceği belirtilmelidir.
//Bu tip örtük bir şekilde System.Object tipini miras almakatadır.
internal class Employee {
...
}
public static void Main() {
Object o = new Employee(); // implicit
Employee e = (Employee) o; // Explicit
}
C#’ta “as” ve “is” Operatörleri
Bir objenin belirli bir türle uyumlu olup olmadığını “is” operatörü ile kontrol edilir. Eğer uyumlu ise true döner değilse false döner. “is” operatörü asla exception fırlatmaz.
Object o = new Object();
Boolean b1 = (o is Object); // b1 is true.
Boolean b2 = (o is Employee); // b2 is false.
Eğer objenin referansı null ise, “is” operatörü false döner. Çünkü kontrol edilecek bir obje yok. Genellikle aşağıdaki gibi kullanılır.
if (o is Employee) {
Employee e = (Employee) o;
// Use e within the remainder of the 'if' statement.
}
Bu şekildeki kullanımda CLR iki defa tip kontrolü yapar. İlk kontrol if bloğu içinde objenin tipe uyumlu olup olmadığı kontrol edildikten sonra içerde dönüştürme işleminde tekrar bir tip kontrolü yapılmaktadır. Bu durumun performansa negatif etkisi olduğu için C# dili “as” operatörünü sunmaktadır. “as” operatörü objenin tip ile uyumlu olup olmadığını kontrol ederken, eğer uyumlu ise objeyi o tipe dönüştürmektedir. Uyumlu değilse referansa null değeri geçmektedir. Kullanımı aşağıdaki gibidir.
Employee e = o as Employee;
if (e != null) {
// Use e within the 'if' statement.
}
Namespace ve Assembly
Namespace’ler tiplerin mantıksal olarak gruplanmasına izin verir. Ve yazılımcılar aradıkları tipleri daha kolay bulabilirler. Örneğin, System.Text namespace’inde text işlemleri üzerinde işlem yapan bir sürü tip vardır. Aynı şekilde System.IO namespace üzerinde I/O işlemleri yapan bir çok tip vardır. Aşağıdaki kod parçasında namespace’ler üzerinden tiplere nasıl erişildiği gösterilmektedir.
public sealed class Program {
public static void Main() {
System.IO.FileStream fs = new System.IO.FileStream(...);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
}
}
Fakat bu şekilde uzun uzun yazmak kodun okunabilirliğini azaltıp karmaşıklığı artırır. Bunun yerine using directive’i ile dosyanın en tepesine bu namespace’ler dahil edildiğinde, namespace’lerin içerdiği bütün tiplere erişilir.
using System.IO; // Try prepending "System.IO."
using System.Text; // Try prepending "System.Text."
public sealed class Program {
public static void Main() {
FileStream fs = new FileStream(...);
StringBuilder sb = new StringBuilder();
}
}
Önemli: CLR namespace’ler ile ilgili bir şey bilmez. Bir tipe eriştiğiniz zaman CLR o tipin tam adını bilmek ister.
Aynı namespace’te bulunan tipler aynı assembly’de olmak zorunda değildir. Aynı namespace’te gruplanmış olan farklı tipler farklı assembly’lerde bulunabilir. Örneğin. System.IO.FileStream tipi MSCorlib.dll’de tanımlıyken, System.IO.FileSystemWatcher tipi de System.dll de yer alıyor.
Ayrıca, bir tek assembly farklı namespace’lerdeki tipleri barındırabilir.Örneğin, System.Int32 ve System.Text.StringBuilder tipleri aynı MSCorlib.dll asembly’de bulunuyor.
.NET platformunda bellek yönetimi CLR tarafından yapılır. Fakat bir yazılımcı olarak tiplerin bellek üzerinde nasıl tutulduğunu bilmek gerekir. Tiplerin arasındaki ilişki ile birbirleri arasında nasıl bir etkileşimleri olduğunu anlamak yeni tipler geliştirmek için çok önemlidir. O yüzden bütün tiplerin System.Object tipinden örtük bir şekilde miras alındığını bilememiz gerekiyor. Yanı sıra tipleri birbirlerine dönüştürme ihtiyacı duyduğumuzda CLR’in tip dönüştürmeye izin verdiğini biliyoruz. Oluşturulan tiplerin namespace’ler ile mantıksal gruplandığını ve assembly’ler ile paylaştırıldığını anlamış olduk. Bu yazı .NET’te tipler konusunun giriş kısmı oldu. Daha sonraki yazılarda tipler üzerine daha detaylı durulacaktır. Umarım faydalı bir içerik olmuştur.
Yararlanılan kaynaklar:
Microsoft CLR via C# Fourth Edition kitabı.