C# Dosya İşlemleri: File, Directory ve FileStream ile Derinlemesine Bakış

Unity oyunlarınızda veri kaydetmek ve yüklemek için C# dosya işlemlerini öğrenin. File, Directory ve FileStream sınıflarıyla dosya ve klasör yönetimini keşfedin.

Giriş: Unity’de Veri Kalıcılığı ve C# Dosya İşlemleri

Oyun geliştirirken, kullanıcı verilerini kaydetmek, yapılandırma dosyalarını okumak veya özel oyun içeriklerini yönetmek gibi birçok senaryoda dosya işlemlerine ihtiyaç duyarız. Unity projelerinizde bu tür görevleri yerine getirmek için C# dilinin sunduğu güçlü dosya ve dizin yönetimi yeteneklerinden faydalanırız. Özellikle System.IO namespace’i altında yer alan File, Directory ve FileStream sınıfları, C# Dosya İşlemleri konusunda temel taşlardır. Bu makalede, bu sınıfların ne işe yaradığını, nasıl kullanıldığını ve Unity projelerinizde veri kalıcılığını nasıl sağlayabileceğinizi detaylıca inceleyeceğiz.

Kısa bir özetle, File sınıfı tekil dosyalar üzerinde basit okuma/yazma işlemleri için idealdir. Directory sınıfı klasör oluşturma, silme ve içeriğini listeleme gibi dizin yönetimi görevleri için kullanılır. FileStream ise daha düşük seviyeli, bayt bazında okuma/yazma gerektiren veya büyük dosyalarla çalışırken performans kritik olan durumlar için tercih edilir. Bu üçlüyü anlayarak, Unity projelerinizde sağlam bir veri yönetim altyapısı kurabilirsiniz.

File Sınıfı: Dosya Temelli Basit İşlemler

File sınıfı, metin dosyaları üzerinde hızlı ve basit işlemler yapmak için tasarlanmıştır. Tek bir çağrıyla tüm dosya içeriğini okuyabilir veya tüm içeriği bir kerede yazabilirsiniz. Bu sınıf statiktir, yani bir nesne oluşturmanıza gerek kalmadan doğrudan metotlarını çağırabilirsiniz.

Dosya Oluşturma ve Yazma

Bir dosyaya metin yazmak için File.WriteAllText() metodunu kullanabilirsiniz. Eğer dosya yoksa oluşturulur, varsa içeriği üzerine yazılır.

using System.IO;
using UnityEngine;

public class FileOperationsExample : MonoBehaviour
{
    void Start()
    {
        string filePath = Application.persistentDataPath + "/oyunKayit.txt";
        string content = "Oyun Puanı: 1250\nOyuncu Adı: Mert";

        try
        {
            File.WriteAllText(filePath, content);
            Debug.Log("Dosya başarıyla yazıldı: " + filePath);
        }
        catch (System.Exception e)
        {
            Debug.LogError("Dosya yazma hatası: " + e.Message);
        }
    }
}

Mevcut bir dosyaya metin eklemek (append) isterseniz, File.AppendAllText() metodunu kullanabilirsiniz.

string logFilePath = Application.persistentDataPath + "/oyunLog.txt";
File.AppendAllText(logFilePath, "\nYeni bir olay kaydedildi: " + System.DateTime.Now);
Debug.Log("Log dosyasına eklendi.");

Dosya Okuma

Bir dosyanın tüm içeriğini okumak için File.ReadAllText() kullanılır.

string readContent = File.ReadAllText(filePath);
Debug.Log("Dosya içeriği:\n" + readContent);

Satır satır okumak isterseniz, File.ReadAllLines() metodunu kullanabilirsiniz. Bu metot, her satırı bir dizi elemanı olarak döndürür.

string[] lines = File.ReadAllLines(filePath);
foreach (string line in lines)
{
    Debug.Log("Satır: " + line);
}

Diğer File İşlemleri

  • File.Exists(path): Belirtilen yolda dosyanın var olup olmadığını kontrol eder.
  • File.Delete(path): Belirtilen dosyayı siler.
  • File.Copy(sourcePath, destinationPath, overwrite): Dosyayı kopyalar. `overwrite` parametresi ile üzerine yazılıp yazılmayacağını belirlersiniz.
  • File.Move(sourcePath, destinationPath): Dosyayı taşır veya yeniden adlandırır.

Directory Sınıfı: Klasör Yönetimi

Directory sınıfı, klasör (dizin) oluşturma, silme, taşıma ve içindeki dosya ve alt klasörleri listeleme gibi işlemleri yönetmek için kullanılır. Tıpkı File sınıfı gibi, Directory sınıfı da statiktir.

Klasör Oluşturma ve Silme

Yeni bir klasör oluşturmak için Directory.CreateDirectory() kullanılır. Klasör zaten varsa, bu metot herhangi bir hata vermez.

string directoryPath = Application.persistentDataPath + "/OyunVerileri";

if (!Directory.Exists(directoryPath))
{
    Directory.CreateDirectory(directoryPath);
    Debug.Log("Klasör oluşturuldu: " + directoryPath);
}
else
{
    Debug.Log("Klasör zaten mevcut: " + directoryPath);
}

Bir klasörü silmek için Directory.Delete() kullanılır. İkinci parametre olarak `true` verirseniz, klasör içindeki tüm dosya ve alt klasörlerle birlikte silinir (recursive silme).

// Klasörü ve içindeki her şeyi sil
Directory.Delete(directoryPath, true);
Debug.Log("Klasör silindi: " + directoryPath);

Klasör İçeriğini Listeleme

Bir klasördeki tüm dosyaları veya alt klasörleri listelemek için aşağıdaki metotları kullanabilirsiniz:

// Klasördeki tüm dosyaları al
string[] files = Directory.GetFiles(directoryPath);
foreach (string file in files)
{
    Debug.Log("Dosya: " + file);
}

// Klasördeki tüm alt klasörleri al
string[] subDirectories = Directory.GetDirectories(directoryPath);
foreach (string subDir in subDirectories)
{
    Debug.Log("Alt Klasör: " + subDir);
}

FileStream Sınıfı: Düşük Seviyeli ve Performanslı İşlemler

File sınıfı basit işlemler için harika olsa da, büyük dosyalarla çalışırken veya bayt bazında okuma/yazma gerektiren durumlarda FileStream sınıfı devreye girer. FileStream, dosyalara doğrudan, bayt akışı (byte stream) şeklinde erişim sağlar. Bu, özellikle ikili (binary) verilerle çalışırken veya performansı optimize etmek istediğinizde çok önemlidir.

FileStream bir nesne olarak oluşturulur ve dosya üzerinde bir “akış” (stream) açar. İşlem bittiğinde bu akışın kapatılması gerekir, aksi takdirde dosya kilitli kalabilir ve diğer uygulamalar veya işlemler tarafından kullanılamaz hale gelebilir. Bu nedenle, FileStream kullanırken using ifadesi kritik öneme sahiptir.

FileStream ile Yazma

Bir FileStream kullanarak bir dosyaya bayt dizisi yazma örneği:

string streamFilePath = Application.persistentDataPath + "/binaryData.dat";
byte[] dataToWrite = { 10, 20, 30, 40, 50 }; // Örnek bayt verisi

using (FileStream fs = new FileStream(streamFilePath, FileMode.Create, FileAccess.Write))
{
    fs.Write(dataToWrite, 0, dataToWrite.Length);
    Debug.Log("FileStream ile veri yazıldı.");
} // 'using' bloğu sonunda FileStream otomatik olarak kapanır ve kaynakları serbest bırakır.

Burada FileMode.Create, dosya yoksa oluşturur, varsa içeriğini siler ve yeniden yazar. FileAccess.Write ise dosyaya yazma erişimi sağlar.

FileStream ile Okuma

byte[] readBuffer = new byte[5]; // Okunacak veriyi tutacak tampon

using (FileStream fs = new FileStream(streamFilePath, FileMode.Open, FileAccess.Read))
{
    int bytesRead = fs.Read(readBuffer, 0, readBuffer.Length);
    Debug.Log(bytesRead + " bayt okundu.");
    foreach (byte b in readBuffer)
    {
        Debug.Log("Okunan bayt: " + b);
    }
}

FileMode.Open, mevcut bir dosyayı açar. FileAccess.Read ise dosyadan okuma erişimi sağlar.

Pratik İpuçları ve En İyi Uygulamalar

1. Unity’de Güvenli Dosya Yolları: Application.persistentDataPath

Unity’de dosya işlemleri yaparken, özellikle farklı platformlarda (Windows, Android, iOS vb.) çalışacak uygulamalar geliştiriyorsanız, dosya yollarını yönetmek önemlidir. Application.persistentDataPath, her platformda uygulamanızın kalıcı veri depolaması için güvenli ve yazılabilir bir yol sağlar. Bu yolu kullanmak, izin sorunlarını ve platformlar arası uyumluluk problemlerini önler. Kullanıcı kaydedilen verileri, ayarları veya oyun durumunu depolamak için idealdir.

string savePath = Application.persistentDataPath + "/GameSaves/MySaveData.json";
// Önce klasörü kontrol et ve oluştur
string saveDirectory = Path.GetDirectoryName(savePath);
if (!Directory.Exists(saveDirectory))
{
    Directory.CreateDirectory(saveDirectory);
}
File.WriteAllText(savePath, "{}"); // Boş JSON dosyası oluşturma

2. Hata Yönetimi: try-catch Blokları

Dosya işlemleri sırasında çeşitli hatalar meydana gelebilir (dosya bulunamadı, erişim reddedildi, disk dolu vb.). Bu tür durumları ele almak için try-catch blokları kullanmak uygulamanızın çökmesini engeller ve kullanıcıya anlamlı geri bildirimler sunmanıza olanak tanır. C# Dosya İşlemleri yaparken bu adımı atlamamak önemlidir.

try
{
    string content = File.ReadAllText("nonExistentFile.txt");
    Debug.Log(content);
}
catch (FileNotFoundException)
{
    Debug.LogError("Belirtilen dosya bulunamadı!");
}
catch (UnauthorizedAccessException)
{
    Debug.LogError("Dosyaya erişim izni yok.");
}
catch (System.Exception e)
{
    Debug.LogError("Beklenmeyen bir hata oluştu: " + e.Message);
}

3. Kaynak Yönetimi: using İfadesi

FileStream gibi kaynakları kullanan nesneler, işleri bittiğinde düzgün bir şekilde kapatılmalı ve sistem kaynaklarını serbest bırakmalıdır. Aksi takdirde, dosya kilitli kalabilir veya bellek sızıntıları oluşabilir. IDisposable arayüzünü uygulayan sınıflar için using ifadesi, bu kaynakların otomatik olarak ve güvenli bir şekilde kapatılmasını sağlar. Bu, FileStream için kesinlikle uyulması gereken bir kuraldır.

// using ifadesi, FileStream nesnesi kapsam dışına çıktığında Dispose() metodunu otomatik çağırır.
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
    // Dosya işlemleri burada yapılır.
}
// fs nesnesi artık kullanılamaz ve kaynakları serbest bırakılmıştır.

Yaygın Hatalar ve Çözümleri

  • Dosya/Klasör Mevcudiyetini Kontrol Etmemek: Bir dosyayı okumaya veya bir klasöre erişmeye çalışmadan önce File.Exists() veya Directory.Exists() ile varlığını kontrol edin. Bu, FileNotFoundException gibi hataları önler.
  • Yanlış Dosya Yolları: Özellikle Windows’tan Linux/macOS’a geçişlerde ters eğik çizgi (\) yerine düz eğik çizgi (/) kullanmaya dikkat edin. Path.Combine() metodunu kullanarak platformdan bağımsız doğru yollar oluşturmak en iyi yaklaşımdır. Ayrıca, Unity’de Application.dataPath veya Application.streamingAssetsPath gibi yolların bazı platformlarda yazılabilir olmadığını unutmayın; bunun yerine Application.persistentDataPath kullanın.
  • FileStream’i Kapatmayı Unutmak: Daha önce de belirtildiği gibi, FileStream gibi kaynakları using ifadesiyle sarmak, bu hatayı tamamen ortadan kaldırır. Aksi takdirde, dosya kilitli kalır ve uygulamanız veya diğer programlar tarafından kullanılamaz.
  • İzin Sorunları: Özellikle mobil cihazlarda (Android, iOS) uygulamanızın dosya sistemine erişmek için belirli izinlere ihtiyacı olabilir. Unity’de bu izinleri `Project Settings` -> `Player` -> `Other Settings` altından veya AndroidManifest.xml gibi dosyalarda manuel olarak ayarlamanız gerekebilir.

Performans ve Optimizasyon Notları

C# Dosya İşlemleri, özellikle oyunlarda performans açısından dikkatli yönetilmelidir. Disk G/Ç (I/O) işlemleri genellikle CPU işlemlerinden çok daha yavaştır ve oyununuzda takılmalara (hiccup) neden olabilir.

  • Büyük Dosyalar için FileStream: Eğer büyük dosyalarla (örn. 1MB’den büyük) çalışıyorsanız veya ikili verileri okuyup yazıyorsanız, File.ReadAllText() veya File.WriteAllText() gibi metotlar yerine FileStream kullanın. FileStream, veriyi parça parça okuyup yazarak bellek kullanımını optimize eder ve daha hızlıdır.
  • Asenkron İşlemler: Oyun ana döngüsünü (main thread) bloke etmemek için uzun süreli dosya işlemlerini asenkron olarak (async/await veya başka bir iş parçacığı/thread üzerinde) gerçekleştirmeyi düşünün. Bu, oyununuzun kullanıcı arayüzünün donmasını veya kare hızının düşmesini engeller. C# async ve await anahtar kelimeleri, bu tür işlemleri kolaylaştırmak için tasarlanmıştır.
  • Sık Yazma İşlemlerinden Kaçının: Oyun durumu kaydetme gibi işlemleri çok sık yapmaktan kaçının. Bunun yerine, belirli aralıklarla veya önemli olaylarda (seviye bitimi, kontrol noktası) toplu olarak kaydetme yapın.
  • Veri Sıkıştırma: Büyük veri setlerini depolarken, disk alanından tasarruf etmek ve G/Ç süresini azaltmak için verileri sıkıştırmayı (örneğin GZipStream kullanarak) düşünebilirsiniz.

Sonuç

Unity ve C# ile dosya işlemleri yapmak, oyun geliştirme sürecinin ayrılmaz bir parçasıdır. File sınıfı basit metin tabanlı işlemler için, Directory sınıfı klasör yönetimi için ve FileStream sınıfı ise düşük seviyeli, performans odaklı veya ikili veri işlemleri için güçlü araçlar sunar. Bu sınıfları doğru bir şekilde kullanarak, oyunlarınız için sağlam bir veri kalıcılığı ve yönetim sistemi oluşturabilirsiniz. Unutmayın, güvenli dosya yolları kullanmak, hata yönetimini uygulamak ve kaynakları doğru bir şekilde serbest bırakmak, sorunsuz ve stabil bir uygulama geliştirmenin anahtarıdır. C# Dosya İşlemleri konusunda bu temel bilgileri ve ipuçlarını uygulayarak projelerinizi bir üst seviyeye taşıyabilirsiniz.

Leave a Reply

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