FileStream, stream (akış) tabanlı bir soyutlama katmanı sağlayarak, dosyaların bayt seviyesinde okunması ve yazılması için kullanılan temel bir sınıftır. Diğer dosya okuma/yazma sınıflarına göre daha düşük seviyede çalışır, bu da geliştiricilere daha fazla kontrol ve esneklik sunar.

Bir FileStream nesnesi, işletim sistemi tarafından yönetilen bir dosya tanıtıcısı (handle) üzerinden çalışır. Bu tanıtıcı, dosyaya özel bir erişim kanalı açar ve akışın yönünü (okuma, yazma veya her ikisi), paylaşım modunu (diğer işlemlerin erişimi) ve tamponlama stratejisini belirler. Bu mekanizma, dosya sistemindeki ham veriye direkt erişim sağlar.

FileStream'ın temel işleyişi, dosyayı bir bayt dizisi gibi ele alır. İşlemler, dosya içindeki belirli bir konumu gösteren bir imleç (cursor veya pointer) üzerinden gerçekleşir. Her okuma veya yazma işlemi bu imlecin bulunduğu konumda başlar ve ardından imleç, işlenen bayt sayısı kadar ilerletilir. Bu yaklaşım, büyük dosyalarda rastgele erişime (random access) olanak tanır.

Kavramsal olarak, Stream sınıf hiyerarşisinin en önemli somut uygulamalarından biridir. Bellek akışı (MemoryStream), ağ akışı (NetworkStream) gibi diğer akış türleriyle aynı temel arayüzü (Read, Write, Seek) paylaşır. Bu, farklı veri kaynaklarına (dosya, bellek, ağ) yönelik kodun tek tip bir şekilde yazılabilmesini sağlar.

Bu düşük seviyeli kontrolün getirdiği güç, aynı zamanda geliştiricinin daha fazla sorumluluk alması gerektiği anlamına gelir.

  • Dosya yollarının doğruluğu ve geçerliliği.
  • Dosya ve akış kaynaklarının uygun şekilde kapatılması (dispose edilmesi).
  • Dosya paylaşım çakışmalarının yönetilmesi.
  • Performans için uygun tampon boyutunun seçilmesi.

FileStream Sınıfı: Özellikler ve Metodlar

FileStream sınıfı, dosya akışının durumunu ve yeteneklerini sorgulamaya yarayan bir dizi kritik özellik sunar. CanRead, CanWrite, CanSeek özellikleri, akışın hangi işlemleri desteklediğini belirtir. Length özelliği, dosyanın bayt cinsinden toplam uzunluğunu verirken, Position özelliği ise akış içindeki geçerli imleç konumunu getirir veya ayarlar. Bu özellikler, akışla çalışmadan önce kontroller yapılmasını sağlar.

Sınıfın ana metodları, System.IO.Stream temel sınıfından türetilmiştir. Read() ve Write() metodları, bir bayt dizisi (byte[]) tamponu kullanarak ham veri transferi gerçekleştirir. Bu metodlar senkron çalışır ve bloklayıcıdır (blocking). Performans açısından, tampon boyutunun doğru seçilmesi bu işlemlerin verimliliğini doğrudan etkiler. Çok küçük tamponlar sık sistem çağrısına, çok büyük tamponlar ise bellek israfına yol açabilir.

Rastgele erişim ihtiyacı için Seek() metodu vazgeçilmezdir. Bu metod, akışın imlecini belirtilen ofset (başlangıç noktasına göre) konumuna taşır. Ofset ve SeekOrigin parametresi (Begin, Current, End) birlikte kullanılarak dosyanın herhangi bir noktasına atlama yapılabilir. Bu özellik, veritabanı dosyaları veya büyük log dosyaları gibi yapılandırılmış dosya formatlarında sıkça kullanılır.

Güncel uygulamalarda asenkron programlama kritiktir. FileStream sınıfı, ReadAsync(), WriteAsync() ve CopyToAsync() metodları ile tam asenkron operasyon desteği sağlar. Bu metodlar, özellikle GUI uygulamalarında veya yüksek ölçeklenebilirlik gerektiren sunucu taraflı uygulamalarda, iş parçacıklarının (thread) bloke olmasını engelleyerek kaynakların verimli kullanılmasını sağlar.

Metot Açıklama Senkron/Asenkron
Read(byte[], int, int) Belirtilen bayt dizisine belirtilen ofset'ten itibaren belirli sayıda bayt okur. Senkron
Write(byte[], int, int) Bayt dizisindeki verileri dosyaya yazar. Senkron
Seek(long, SeekOrigin) Akış içinde yeni bir konuma geçiş yapar. Senkron
ReadAsync(byte[], int, int) Okuma işlemini asenkron olarak gerçekleştirir. Asenkron
Flush() Ara bellekteki tüm verilerin temel depolama birimine yazılmasını sağlar. Senkron

Kaynak yönetimi açısından en kritik metodlardan biri Close() veya Dispose()'dur. FileStream nesnesi, temelindeki işletim sistemi kaynağını (dosya tanıtıcısını) kullanır. Bu kaynağın açık kalması, dosya kilitlenmelerine ve kaynak sızıntılarına neden olur. Bu riski ortadan kaldırmak için en güvenli yol using deyimi ile birlikte kullanmaktır.


using (FileStream fs = new FileStream("veri.dat", FileMode.Open))
{
    // FileStream ile işlemler yapılır.
    byte[] buffer = new byte[1024];
    int bytesRead = fs.Read(buffer, 0, buffer.Length);
} // Bu bloktan çıkıldığında fs.Dispose() otomatik çağrılır, dosya kapanır.

Dosya Erişim Modları ve Paylaşım Senaryoları

FileStream oluştururken kullanılan FileMode ve FileAccess parametreleri, dosya ile nasıl etkileşime geçileceğini belirleyen en temel kontrollerdir. FileMode sabiti, dosyanın var olup olmadığı durumda ne yapılacağını tanımlar. Örneğin, FileMode.Create yeni bir dosya oluşturur (varsa üzerine yazar), FileMode.Open var olan bir dosyayı açar, FileMode.Append ise dosyayı açıp imleci sonuna konumlandırarak yazmaya hazırlar.

Bu modlar, uygulamanın amacına göre dikkatle seçilmelidir. FileMode.OpenOrCreate gibi seçenekler, esnek bir yaklaşım sağlarken, yanlış kullanım veri kaybına yol açabilir. Örneğin, yalnızca okuma amaçlı bir dosyayı FileMode.Truncate ile açmak, tüm içeriğin silinmesine neden olur.

Dosya paylaşımı, çoklu işlemli ortamlarda kritik bir konudur. Varsayılan olarak, bir FileStream dosyayı özel yazma erişimi ile açar. Yani, sizin işleminiz yazma için dosyayı açtığında, başka bir işlem aynı dosyayı yazma modunda açamaz. Ancak, FileShare sabiti kullanılarak bu davranış değiştirilebilir. FileShare.Read parametresi, diğer işlemlerin aynı dosyayı okuma amaçlı açmasına izin verirken, FileShare.ReadWrite hem okuma hem yazma paylaşımına olanak tanır.

Bu paylaşım mekanizmaları, dosya kilitleme (file locking) stratejilerinin temelini oluşturur. Örneğin, bir log dosyasına sürekli ekleme yapan bir uygulama, dosyayı FileShare.Read ile açarak diğer uygulamaların logları anlık olarak okumasına izin verebilir. Bu, sistem kaynaklarının verimli ve çakışmasız kullanımını sağlar.

  • Özel Erişim (Exclusive): FileShare.None - Dosya tamamen kilitlenir.
  • Okuma Paylaşımı: FileShare.Read - Diğerleri okuyabilir, yazamaz.
  • Yazma Paylaşımı: FileShare.Write - Diğerleri yazabilir, okuyamaz (nadir kullanılır).
  • Tam Paylaşım: FileShare.ReadWrite - Hem okuma hem yazma paylaşılır.

Paylaşım ve erişim modlarının yanlış kombinasyonları IOException istisnasına yol açar. Örneğin, bir dosya FileShare.None ile açılmışken, başka bir işlem aynı dosyaya erişmeye çalışırsa bu istisna fırlatılır. Bu nedenle, güçlü hata yakalama mekanizmaları ve yeniden deneme mantıkları geliştirilmelidir.

Ağ paylaşımındaki dosyalar veya harici depolama birimleri gibi senaryolarda, dosya erişimi ek gecikmelere ve tutarsızlık risklerine maruz kalır. Bu gibi durumlarda, FileMode ve FileShare ayarlarının yanı sıra, işlemlerin atomik olup olmadığı ve veri bütünlüğünün nasıl korunacağı da dikkate alınmalıdır.

Performans ve Kaynak Yönetimi Açısından Kritik Hususlar

FileStream performansı, büyük ölçüde tamponlama (buffering) mekanizmasına bağlıdır. Varsayılan olarak, FileStream dahili bir yazma tamponu kullanır. Write() çağrıları önce bu tampona yazılır; tampon dolduğunda veya Flush() metodu çağrıldığında fiziksel diske yazılır. Bu, küçük ve sık yazma işlemlerinde performansı büyük ölçüde artırır.

Ancak, bu avantaj bir riski beraberinde getirir: Tamponlanmış veriler, Flush() çağrılmadan veya akış kapatılmadan önce sistem çökmesi veya güç kesintisi durumunda kaybedilebilir. Kritik veri bütünlüğü gerektiren uygulamalarda (veritabanı işlem günlükleri gibi), tamponlamanın devre dışı bırakılması veya her yazma sonrası Flush() çağrılması gerekebilir, ancak bu ciddi bir performans düşüşüne yol açar.

Kaynak yönetimi, FileStream kullanımında en sık hata yapılan alandır. Dosya tanıtıcısı (handle), işletim sistemi tarafından yönetilen sınırlı bir kaynaktır. Using bloğu kullanılmadan açılan ve kapatılmayan her FileStream, bu kaynağı süresiz olarak işgal eder. Bu durum, uzun süre çalışan uygulamalarda "Too many open files" benzeri hatalara ve uygulamanın veya sunucunun çökmesine neden olabilir.

Büyük dosyalarla çalışırken bellek yönetimi de önemlidir. Tüm dosyayı tek seferde bir bayt dizisine okumak (File.ReadAllBytes) basit görünebilir, ancak gigabayt boyutundaki dosyalar için uygulamanın bellek tüketimini kabul edilemez seviyelere çıkarır. Bunun yerine, dosyanın küçük parçalar halinde akış kullanılarak okunması veya işlenmesi çok daha verimlidir. Bu, akış tabanlı mimarinin temel avantajlarından biridir.

Son olarak, FileStream'in ömrü boyunca oluşabilecek istisnalar düzgün şekilde ele alınmalıdır. try-catch-finally blokları içinde, finally bölümünde akışın kapatıldığından emin olunmalıdır. Asenkron metodlarda bile, DisposeAsync() çağrılmalı veya await using deyimi kullanılmalıdır. Bu sayede, hem bellek hem de işletim sistemi kaynakları kararlı ve tahmin edilebilir bir şekilde serbest bırakılır.