C# Diziler ve List: Koleksiyon Temelleri

C# programlamada veri depolamanın temel yolları olan diziler (Array) ve dinamik listeler (List) arasındaki farkları, kullanım alanlarını ve en iyi pratikleri öğrenin.

Unity ve C# ile oyun geliştirirken veya herhangi bir yazılım projesi üzerinde çalışırken, verileri düzenli bir şekilde depolamak ve yönetmek kritik öneme sahiptir. Bu noktada, C# programlama dilinin sunduğu koleksiyon yapıları devreye girer. En temel ve sık kullanılan koleksiyon türlerinden ikisi, diziler (Arrays) ve genel listeler (List<T>)‘dir. Bu makalede, C# Diziler ve List<T> yapılarını derinlemesine inceleyecek, aralarındaki farkları, ne zaman hangisini kullanmanız gerektiğini ve performans ipuçlarını ele alacağız. Bu sayede, veri yönetimi becerilerinizi bir üst seviyeye taşıyabileceksiniz.

Diziler (Arrays): Sabit Boyutlu Güç

Diziler, aynı türden birden fazla öğeyi sıralı bir şekilde depolamanın en temel yoludur. Bir dizi tanımlandığında, boyutu sabittir ve sonradan değiştirilemez. Bu özellik, dizileri belirli sayıda öğeyi depolamak için ideal kılar.

Dizi Tanımlama ve Başlatma

Bir dizi tanımlamak için öğelerin türünü, ardından köşeli parantezleri [] ve dizinin adını belirtmeniz gerekir. Başlatma sırasında dizinin boyutunu veya başlangıç değerlerini atayabilirsiniz.

// 5 elemanlı bir int dizisi tanımlama
int[] sayilar = new int[5];

// Başlangıç değerleriyle dizi tanımlama
string[] isimler = { "Ayşe", "Mehmet", "Zeynep" };

// Dizi elemanlarına tek tek değer atama
sayilar[0] = 10;
sayilar[1] = 20;
// ...

Dizilere Erişim ve Döngüler

Dizi elemanlarına sıfırdan başlayan indeks numaraları aracılığıyla erişilir. Örneğin, bir dizinin ilk elemanı diziAdi[0] ile elde edilir. Dizilerle çalışırken for veya foreach döngüleri sıklıkla kullanılır.

for (int i = 0; i < isimler.Length; i++)
{
    Console.WriteLine(isimler[i]);
}

// foreach döngüsü ile daha okunaklı erişim
foreach (string isim in isimler)
{
    Console.WriteLine(isim);
}

Length özelliği, bir dizideki eleman sayısını verir.

Çok Boyutlu Diziler (Kısaca)

C# ayrıca matrisler gibi çok boyutlu dizileri de destekler. Örneğin, iki boyutlu bir dizi (bir tablo gibi) şu şekilde tanımlanabilir:

int[,] matris = new int[3, 3]; // 3x3 bir matris
matris[0, 0] = 1;

Ne Zaman Dizi Kullanmalı?

  • Eleman sayısı önceden biliniyorsa ve değişmeyecekse.
  • Yüksek performans gerektiren ve doğrudan bellek erişimi istenen durumlarda.
  • Bellek kullanımı üzerinde daha fazla kontrol gerektiğinde.
  • Veri setinin boyutu sabit olduğunda ve sık sık ekleme/silme yapılmayacaksa.

List<T>: Dinamik ve Esnek Koleksiyon

List<T>, C# dilinde en çok tercih edilen dinamik boyutlu koleksiyon türüdür. T, listenin depolayacağı veri tipini (örneğin int, string, özel bir sınıf) belirtir. Dizilerin aksine, List<T>‘nin boyutu çalışma zamanında dinamik olarak artırılabilir veya azaltılabilir.

List<T> Tanımlama ve Kullanımı

List<T> kullanmak için System.Collections.Generic namespace’ini dahil etmeniz gerekir.

using System.Collections.Generic;

// Bir int listesi tanımlama
List<int> puanlar = new List<int>();

// Başlangıç değerleriyle liste tanımlama
List<string> oyuncular = new List<string> { "Ali", "Veli", "Can" };

Eleman Ekleme, Silme ve Güncelleme

List<T>, elemanları yönetmek için birçok kullanışlı metoda sahiptir:

  • Add(item): Listenin sonuna eleman ekler.
  • Remove(item): Belirtilen elemanın ilk oluşumunu siler.
  • RemoveAt(index): Belirtilen indeksteki elemanı siler.
  • Insert(index, item): Belirtilen indekse eleman ekler.
  • Clear(): Tüm elemanları temizler.
puanlar.Add(100);
puanlar.Add(250);
puanlar.Insert(0, 50); // Başa 50 ekle

Console.WriteLine(puanlar[0]); // Çıktı: 50

puanlar.Remove(250); // 250 değerini sil
// veya
puanlar.RemoveAt(0); // İlk elemanı sil

// Eleman güncelleme (dizilerdeki gibi indeks ile)
puanlar[0] = 120;

Listenin eleman sayısını Count özelliği ile alabilirsiniz.

List<T> ve Boyut Yönetimi

List<T>, dahili olarak bir dizi kullanır. Liste dolduğunda, daha büyük yeni bir dizi oluşturulur ve eski elemanlar yeni diziye kopyalanır. Bu işlem otomatik olarak gerçekleşir ancak performans maliyeti vardır. Capacity özelliği, listenin şu anki dahili dizi kapasitesini gösterir. Count ise gerçekten kullanılan eleman sayısını.

Ne Zaman List<T> Kullanmalı?

  • Eleman sayısının çalışma zamanında değişeceği durumlarda.
  • Sık sık eleman ekleme, silme veya arama yapmanız gerektiğinde.
  • Daha fazla esneklik ve hazır metotlar aradığınızda.
  • Veri setinin boyutu başlangıçta tam olarak bilinmediğinde.

C# Diziler ve List<T> Karşılaştırması

C# Diziler ve List<T> arasındaki temel fark, boyut esnekliğidir. Diziler sabit boyutluyken, List<T> dinamik olarak büyüyebilir ve küçülebilir. Bu fark, performans ve bellek kullanımı üzerinde önemli etkilere sahiptir.

Performans ve Bellek Farkları

  • Diziler:
    • Sabit boyutlu oldukları için bellekleri önceden ayrılır ve daha verimli kullanılır.
    • Elemanlara doğrudan indeks ile erişim çok hızlıdır (O(1) karmaşıklık).
    • Ekleme/silme işlemleri genellikle maliyetlidir çünkü yeni bir dizi oluşturulup elemanların kopyalanması gerekebilir (manuel olarak yapılması gerekir).
  • List<T>:
    • Dinamik boyutludur, ancak bu esneklik bir maliyetle gelir. Liste dolduğunda, dahili dizinin yeniden boyutlandırılması (yeni bir dizi oluşturma ve elemanları kopyalama) performansı etkileyebilir.
    • Elemanlara indeks ile erişim hala hızlıdır (O(1)).
    • Ekleme ve silme işlemleri, dahili dizinin yeniden boyutlandırılmasını gerektirebilir ve bu da O(N) karmaşıklığa yol açabilir (N, eleman sayısı).
    • Genel olarak, diziye göre biraz daha fazla bellek kullanabilir (kapasite fazlalığı nedeniyle).

Pratik İpuçları

1. Doğru Koleksiyonu Seçmek

Yukarıdaki karşılaştırmayı göz önünde bulundurarak, projenizin gereksinimlerine en uygun koleksiyonu seçin. Eğer eleman sayısı sabitse ve performans kritikse dizi tercih edin. Eğer dinamik bir yapıya ihtiyacınız varsa ve sık sık eleman ekleyip çıkaracaksanız List<T> kullanın. Unutmayın, doğru koleksiyonu seçmek uygulamanızın performansını ve kodunuzun okunabilirliğini doğrudan etkiler.

2. foreach Döngüsünün Gücü

Hem diziler hem de List<T> için elemanları yinelemek (iterate etmek) üzere foreach döngüsünü kullanmak, kodunuzu daha temiz ve okunabilir hale getirir. Özellikle indeks değerleriyle uğraşmak istemediğinizde idealdir.

List<string> ogrenciler = new List<string> { "Ayşe", "Bora", "Cem" };
foreach (string ogrenci in ogrenciler)
{
    Console.WriteLine($"Öğrenci Adı: {ogrenci}");
}

3. List<T> Kapasitesini Önceden Belirlemek

Eğer bir List<T>‘ye kaç eleman ekleneceğini yaklaşık olarak biliyorsanız, listeyi başlangıçta uygun bir kapasiteyle başlatmak, yeniden boyutlandırma işlemlerinin maliyetini azaltarak performansı artırabilir.

// Yaklaşık 100 eleman eklenecekse
List<int> buyukListe = new List<int>(100);
for (int i = 0; i < 100; i++)
{
    buyukListe.Add(i);
}
// Bu durumda liste muhtemelen hiç yeniden boyutlandırılmayacaktır.

4. LINQ ile Koleksiyon İşlemleri

C#’ın LINQ (Language Integrated Query) özelliği, hem diziler hem de List<T> gibi koleksiyonlar üzerinde güçlü sorgulama ve manipülasyon işlemleri yapmanızı sağlar. Filtreleme, sıralama, gruplama gibi birçok işlemi tek satırda gerçekleştirebilirsiniz.

using System.Linq;

List<int> sayilar = new List<int> { 5, 12, 3, 8, 15 };

// Tek sayilari filtrele ve sırala
var tekSayilar = sayilar.Where(s => s % 2 != 0).OrderBy(s => s).ToList();
// tekSayilar: [3, 5, 15]

Yaygın Hatalar ve Çözümleri

1. IndexOutOfRangeException Hatası

Bu, hem dizilerde hem de List<T>‘de en sık karşılaşılan hatalardan biridir. Bir koleksiyonun geçerli indeks aralığının dışındaki bir elemana erişmeye çalıştığınızda oluşur. Diziler için 0 ile Length - 1, List<T> için 0 ile Count - 1 aralığına dikkat edin.

int[] dizi = new int[3];
dizi[3] = 10; // Hata! Geçerli indeksler 0, 1, 2

List<int> liste = new List<int> { 1, 2 };
Console.WriteLine(liste[2]); // Hata! Geçerli indeksler 0, 1

Çözüm: Elemanlara erişmeden önce indeksin geçerli aralıkta olup olmadığını kontrol edin veya döngü sınırlarını doğru ayarlayın.

2. Döngü İçinde List<T> Elemanlarını Silmek

List<T> üzerinde foreach döngüsü ile gezinirken eleman silmeye çalışmak, koleksiyonun değiştiği ve iterasyonun bozulduğu anlamına geldiği için hataya yol açabilir veya beklenmedik sonuçlar doğurabilir.

List<string> isimler = new List<string> { "A", "B", "C", "D" };
foreach (string isim in isimler)
{
    if (isim == "B")
    {
        isimler.Remove(isim); // Hata veya beklenmedik davranış!
    }
}

Çözüm: Elemanları silerken tersine döngü kullanın veya silinecek elemanları ayrı bir listeye toplayıp döngüden sonra silme işlemini yapın. Alternatif olarak, LINQ’in Where() metodunu kullanarak yeni bir liste oluşturabilirsiniz.

// Çözüm 1: Tersine döngü
for (int i = isimler.Count - 1; i >= 0; i--)
{
    if (isimler[i] == "B")
    {
        isimler.RemoveAt(i);
    }
}

// Çözüm 2: Yeni liste oluşturma (daha güvenli ve modern)
isimler = isimler.Where(isim => isim != "B").ToList();

3. Dizi Boyutunu Değiştirmeye Çalışmak

Dizilerin boyutu sabittir. Bir dizi tanımlandıktan sonra boyutunu değiştiremezsiniz. Yeni bir boyutta diziye ihtiyacınız varsa, yeni bir dizi oluşturup eski elemanları kopyalamanız gerekir.

int[] eskiDizi = new int[3];
// eskiDizi.Length = 5; // Hata! "Length" özelliği salt okunurdur.

// Çözüm: Yeni bir dizi oluşturup elemanları kopyalayın
int[] yeniDizi = new int[5];
Array.Copy(eskiDizi, yeniDizi, eskiDizi.Length);

Bu tür durumlarda, dinamik boyutlu List<T> kullanmak daha pratik bir çözüm olabilir.

Performans ve Optimizasyon Notları

  • Diziler, elemanlara doğrudan bellek adresleri üzerinden erişim sağladığı için genellikle List<T>‘den daha hızlıdır, özellikle okuma işlemleri için.
  • List<T>‘nin yeniden boyutlandırma maliyeti, çok sık ekleme/silme yapıldığında performansı düşürebilir. Eğer bu durumla karşılaşıyorsanız, başlangıç kapasitesini doğru ayarlayarak veya daha nadiren yeniden boyutlandırma yapan başka koleksiyon türlerini (örn. LinkedList<T>) değerlendirerek bu maliyeti azaltabilirsiniz.
  • Küçük veri setleri için C# Diziler ve List<T> arasındaki performans farkı çoğu zaman ihmal edilebilir düzeydedir. Önemli olan, büyük veri setleri veya yüksek frekanslı işlemler söz konusu olduğunda doğru seçimi yapmaktır.
  • Unity’de, özellikle oyun döngüsünde (Update metodunda) sık sık yeni List<T> veya dizi oluşturmaktan kaçının. Bu durum, çöp toplayıcıyı (Garbage Collector) tetikleyerek oyununuzda takılmalara (hiccups) neden olabilir. Mümkünse koleksiyonları önceden oluşturun ve yeniden kullanın.

Sonuç

C# Diziler ve List<T>, C# programlamanın ve Unity oyun geliştirmenin temel taşlarından ikisidir. Diziler, sabit boyutlu, performans odaklı veri depolama için idealdir; List<T> ise dinamik, esnek ve kullanımı kolay yapısıyla değişken boyutlu veri setleri için mükemmel bir çözümdür. Her ikisinin de avantajlarını ve dezavantajlarını anlamak, kodunuzu daha verimli, okunabilir ve hatasız hale getirmenize yardımcı olacaktır. Projenizin gereksinimlerini dikkatlice değerlendirerek doğru koleksiyon türünü seçmek, başarılı bir yazılım geliştirme sürecinin anahtarıdır.

Leave a Reply

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir