C# Basic I/O Operations (Temel Girdi/Çıktı İşlemleri)

Merhaba arkadaşlar,

Bir önceki yazımda System İsim Alanından bahsettik. Kısacası System İsim Alanı yaygın olarak kullanılan değer ve referans veri türleri (value & reference data type), olaylar ve olay işleyicileri (events & event handlers), arayüzler (interfaces), öznitelikler (attributes) ve işleme sırasındaki istisnaları tanımlayan ana ve temel sınıfları içerir. C# System namespace yazıma buradan ulaşabilirsiniz.

Bu yazımda C# programlama dilinde Temel I/O işlemlerini ele alacağız. I/O kelimesi Input/Output sözcüklerinden gelir. Türkçe karşılığı ise Girdi/Çıktı olarak çevrilmiştir. Girdi çıktılar ile temel metin işlemlerini yapan çeşitli sınıfları ve bu sınıflar arası ilişkinin nasıl yönetildiğine bakacağız.

Bu yazıda bahsedilecek konular

  • C# I/O Sistemi
  • Dosya ve Klasör İşlemleri
    • Director Sınıfı
    • File Sınıfı
    • DirectoryInfo Sınıfı
    • Path Sınıfı
  • Dosya Yazma ve Okuma İşlemleri
    • FileStream Sınıfı
    • FileStream ile Yazma Okuma
    • Dosya Akımı ile Text İşlemleri Yapmak
    • StreamReader sınıfı
    • StreamWriter Sınıfı
  • BinaryWriter ve BinaryReader Sınıfları
  • Console I/O İşlemleri
    • Standart Akımların Yönlendirilmesi

C# I/O Sistemi

C#’ta I/O sistemi ile ilgili bütün sınıflar System.IO isim alanında bulunurlar. I/O sistemi C#’ta stream yani akım dediğimiz yapılar üzerine kuruludur. Akımlar bir girdi veya çıktı sisteminde bilgiyi byte düzeyinde okuyan bir soyut birimdir. Yani akım ile dosyadan bilgi okunabileceği gibi bir başka girdi olan klavyeden de bilgi okuyabiliriz. En geneliyle bilgi akışı varsa burada stream (akım) vardır diyebiliriz.

Bu stream ile ne yapabiliriz? Örneğin sürekli kullandığımız Console sınıfının WriteLine() metodu ile yazdıklarımızın aynısını aslında bir dosyaya da kaydedebiliriz. Standart olarak biliyoruz ki WriteLine() metodu çıktısı ekrana yazdırmaktadır. Buradaki standart akımlar Console.In( standart giriş ya da klavye), Console.Out (standart çıkış ya da ekran) ve Console.Error (standart hata akımı ya da ekran)

C#’ta akımlarla ilgili işlemler System.IO isim alanında bulunan Stream sınıfı ile yapılmaktadır. Stream sınıfı bir akımın destekleyebileceği minimum akıma yazma ve akımdan okuma gibi özellikleri taşır. Diğer özel akım sınıfları da Stream sınıfından türemişlerdir. Bunlar; FileStream, MemortStrem, BufferedStrem gibi.

Şimdi Stream sınıfının özelliklerine bakalım.

MetotAçıklama
public int Read(byte[] buffer, int offset, int count)Bu metot ile buffer[index]’ten itibaren count sayısı kadar byte bilgi akımdan buf dizisine okunur.
public int ReadByte()Bu metot ile akımdan bir byte okunur ve int olarak geri döndürülür
public void Write(byte[] buffer, int offset, int count);Bu metot ile buffer[index] den count sayısı kadar byte bilgi akıma yazılır.
publicvoid WriteByte(byte value);Bu metot ile akıma value ile gelen bir byte yazılır.
public long Seek(long offset, SeekOrigin origin);Bu metot ile SeekOrigin Numaralandırması ile belirtilmiş konumdan offset kadar ileriye ötelenir.
void Flush()Bu metot ile akımla ilgili tamponlanmış bilgiler silinir ve akımdaki bilgiler ilgili fiziksel birime iletilir.
void Close()Bu metot ile akım kapatılır. Stream.Close() çağrıldığında akım ile ilgili kaynaklar iade edilir.

Stream sınıfına ait çok kullanılan metotlar yukarıdaki gibidir.

Dosya ve Klasör İşlemleri

Dosya ve klasör işlemlerini yapan Directory, File, FileInfo, Path ve DirectoryInfo sınıfları sayesinde dosyalar ve klasörler üzerinde kopyalama, isim değiştirme gibi temel işlemleri yapmaktayız. Bu sınıfların I/O sistemi ile çok ilgisi olmamasına rağmen genellikle I/O sistemleri ile çok kullanılırlar.

File ve FileInfo sınıfları dosya sistemindeki dosyaları temsil ederken, Directory ve DirectoryInfo sınıfları ise klasörleri temsil etmektedir. Path sınıfı ise dosya ve klasörlerin yol (path) bilgileri ile ilgili işlem yapmak için kullanılır.

File ve Directory sınıfları sadece statik metot içermektedirler. Yani herhangi bir nesne tanımlamadan bütün metotlara ulaşabilirsiniz. Genellikle sadece bir dosya veya klasör üzerinde işlem yapılacaksa bu sınıflar kullanılır. Klasör içindeki dosyalar ile ya da belirli dosya grubu ile ilgili işlem yapmak için FileInfo ve DirectoryInfo sınıflarından yararlanılır.

Directory Sınıfı

Directory sınıfı System.IO isim alanında bulunan ve bütün metotları statik olan klasörlerle işlem yapmamızı sağlayan bir sınıftır. Directory sınıfını kullanarak metotlarını uygulama üzerinde yapalım

File Sınıfı

File sınıfının bazı metotları Directory sınıfı ile aynıdır. Tek farkı klasör yerine dosya üzerinden işlem yapıyor olmasıdır. File sınıfı ile özelleşmiş metotları örnek uygulama üzerinden yapalım.

Oluşan myfile.json

Pratik kazanmak için bilgisayarımızın herhangi bir sürücüsünden soya ve klasörleri çekip Attributes (öznitelik) ekrana yazdıran örnek bir program yazalım

DirectoryInfo Sınıfı

DirectoryInfo sınıfı tek bir dizin ile ilgili bilgileri elde etmek için kullanırız. DirectoryInfo sınıfını kullanarak örnek bir uygulama üzerinde özellik ve metotlarını inceleyelim. Bu metotlar statik olmadığı için nesne oluşturduktan sonra erişebiliriz.

FileInfo Sınıfı

FileInfo sınıfı tek bir dosya ile ilgili özellikleri içerir. FileInfo sınıfını kullanarak örnek bir uygulama üzerinde özellik ve metotlarını inceleyelim. Bu metotlar statik olmadığı için nesne oluşturduktan sonra erişebiliriz.

FileInfo sınıfını incelerken pc’de bir txt dosyası oluşturup dosyanın yolunu kullanabilirsiniz.

Path Sınıfı

Path sınıfı string türünden aldığı bir yol bilgisi üzerinde çeşitli işlemler yapan statik üye metotlara sahip bir sınıftır.

Path sınıfını incelerken pc’de bir txt dosyası oluşturup dosyanın yolunu kullanabilirsiniz.

Path sınıfının en önemli metodu GetTempFileName() metodudur. Bu metot sistemimizin temp klasöründe oluşturduğu bir dosyanın ismini döner. Dosyalarla ilgili geçici işlem yapmak istiyorsak bu metodu kullanabiliriz.

Dosya Yazma ve Okuma İşlemleri

C# programlama dilinde dosyalardan okuma ya da dosyalara yazma akımlarla olmaktadır. Akımlar (stream) içinde bilgi depolayan soyutlamalardır. System.IO isim alanında dosya yazma okuma ile ilgili birçok sınıf bulunmaktadır. Ayrıca binary (ikili) düzeyde işlem yapan sınıfla da mevcuttur.

Akıma yazma ve akımdan okuma sınıfları System.IO isim alanındaki organizasyon şeması

Bir dosyadan byte düzeyinde veri okuma ve veri yazma işlemleri için FileStream sınıfı kullanılır. StreamReader ve StreamWriter sınıfları ile metin tabanlı dosyalarda okuma ve yazma işlemleri yapılır. BinaryReader ve BinaryWriter sınıfları ise binary (ikili) dosyalarda işlem yapmak için kullanılır. StringReader ve StringWriter sınıfları ile dosyadan formatlı bir şekilde yazı okuma veya dosyaya formatlı bir şekilde yazı yazmak için kullanılır.

FileStream Sınıfı

FileStream sınıfı ile diskte bir dosya açılır. StreamReader ve StreamWriter sınıfları yardımı ile açılan dosya üzerinde işlemler yapılır. Dosya üzerinde metin tabanlı işlemler yapılabildiği gibi byte tabanlı işlemlerde yapabiliriz. Bir dosyadan byte üzerinden bilgi almak binary işlem yapmak demektir. Zaten Bilgisayar bilimleri kavramında dosya kavramı byte düzeyindedir. Dosyadaki bilgilere anlam katan bizleriz. Örneğin byte düzeyinde alınan bilgileri karaktere çevirerek metin tabanlı işlemler yaparız.

FileStream nesnesi birçok şekilde oluşabilir. FileStream nesnesini oluştururken FileMode, FileAccess, FileShare numaralandırmaları ile dosya üzerinde nasıl bir hakimiyetimiz olacağını söylüyoruz.

Dosya ile ilişkimiz bittiğinde FileStream sınıfının Close() metodu ile tutulan kaynaklar boşaltılır. Böylece ilgili dosya başka prosesler tarafından da işlenebilir hale gelir.

FileStream sınıfının Read() ve ReadByte() metotları dosya akımından byte düzeyinde veri okumamızı sağlar.

ReadByte() metodu bir byte’lık veri okur ve akımdaki okuma pozisyonunu bir artırır ki bir sonraki okumada aynı değer okunmasın. Okuma yapmadığı zaman -1 değerini döndürür

Read(byte[] dizi, int baslangic, int adet) metodu ile adet kadar byte dizinin akımından okunur, okunan bu veriler byte dizisine dizi[baslangic] elemanından itibaren yerleştirilir. Geri dönüş değeri okunan byte miktarını döner.Eğer dosyanın sonuna gelip okuma yapılmadığını bildirmek için 0 değerini döndürür.

Console uygulaması üzerinde yazdığımız örnek bir FileStream uygulamasını ekranda gösterecek bir uygulama ile bu bilgileri pekiştirelim. Console uygulamasının çalıştığı ana program olan Program.cs dosyasını FileStream nesnesine yol olarak veriyoruz. Bunu birçok yöntem ile yapabilirsiniz. Aşağıdaki args parametresinden .Net CLI komutu kullanılarak Program.cs gönderilmiştir. Sizde path değişkenine statik olarak string türünde “Program.cs” yazıp uygulamayı çalıştırabilirsiniz. Program.cs dosyasındaki verileri byte düzeyinde ReadByte() ile aldıktan sonra dosyada okunacak bilgi olana kadar döngüye alıyoruz. Her döngüde okuduğumuz byte bilgisini anlamlı hale getirmek için char tipine tür dönüşümünü uyguluyoruz.

Şimdi ise bir dosya üzerinde byte düzeyinde bir veri yazmak için kullanulan Write() ve WriteByte() metotlarına bakalım

void WriteByte(byte veri) Dosya yakımına bir byte’lık veri aktarmak için kullanılır. Başarısız olursa istisnai durum meydana gelir

void Write(byte[] dizi, int baslangic, int adet) Dosya yakımına bir byte dizisi veri aktarmak için kullanılır. Bu metot ile dizi[baslangic] elemanından itibaren adet kadar byte veri dosya akımına aktarılır. Başarısız olursa istisnai durum meydana gelir.

Dosya akımına yazılan veriler hemen dosyaya işlenmez. Dosya akımı tamponlama mekanizması ile çalıştığı için belirli bir miktar veri yazılana kadar dosya güncellenmez. Ancak FileStream sınıfının Flush() metodu ile istediğimiz anda tamponu boşaltıp dosyayı tampondaki bilgilerle güncelleyebiliriz.

Şimdi dosya akımına yazma işlemi ile ilgili örnek yapalım. Console uygulaması üzerinden verileri şifreleyerek başka bir dosyaya aktarmaya çalışacağız. Şifreleme işlemini yaparken de dosya akımına aktarılan her byte için XOR işlemine tabi tutacağız. XOR işlemi geri dönüşümlü olmasından dolayı aynı şifre ile şifrelenmiş verilerimizi çözebileceğiz.

Özellikler

  • bool CanRead: Bu özellik ile akımdan okuma yapılıp yapılmayacağı öğrenilir
  • bool CanSeek: Bu özellik ile akımda konumlandırma yapılıp yapılmayacağı öğrenilir
  • bool CanWrite: Bu özellik ile akıma yazma yapılıp yapılmayacağı öğrenilir
  • long Position: Bu özellik ile akımda o anda bulunan konum bilgisi öğrenilir
  • long Length: Bu özellik ile akımın byte olarak büyüklüğü öğrenilir.

Dosya Akımı ile Text İşlemleri Yapmak

Bir dosya akımını metin tabanlı veriler şeklinde okunabilir. Bu tür durumlar text dosyalarından okuma ve yazma yaparken karşımıza çıkar. Metin tabanlı dosyalarla işlem yapmak için StreamReader ve StreamWriter sınıflarını kullanabiliriz. Bu sınıflar ile bir dosya akımı okunduğunda otomatik olarak metin moduna çevrilir. StreamReader ve StreamWriter sınıflarını kullanırken karakter kodlamasına çok dikkat etmeyeceğiz. Çünkü bu sınıflar sistemin karakter kodlamasına göre kendini ayarlar.

StremReader Sınıfı

Dosya akımından karakter tabanlı okuma yaparken kullandığımız sınıf. Diğer kaynaklarda olduğu gibi StreamReader nesneleri ile ilişkimiz bittiğinde Close() metodunu kullanarak kaynakların geri iade edilmesini sağlamamız gerekiyor.

StreamReader sınıfını kullanarak dosya üzerinden okuma yapıp sonuçları ekrana yazdıran program yapalım.

StreamWriter Sınıfı

StreamWriter sınıfı ile dosya akımına karakter tabanlı bilgileri yazmak için kullanırız. StreaReader sınıfında olduğu gibi Close() metodu ile StreamWriter nesnelerine ilişkin kaynaklar iade edilir.

StreamReader sınıfını kullanarak dosya üzerine isim yazdıran program yapalım.

BinaryWriter ve BinaryReader Sınıfları

Bu sınıflar ile dosya akımına istenilen türde verilerin yazılması için kullanılır.

Programı çalıştırdıktan sonra Ekrana yazılan bilgiler anlamlı iken ilgili dosyaya baktığımızda bilgiler anlamsızdır. Bu tür dosyalara binary (ikili) dosya denilmektedir.

Console I/O İşlemleri

I/O işlemleri için gerekli olan sınıflardan System.IO isim alanında olmayan tek sınıf Console sınıfıdır. Console sınıfını şimdiye kadar örnekleri pekiştirirken gerek ekrana bilgi yazdırmak gerekse kullanıcıdan bilgi almak için kullandık. Konsol I/O işlemleri için önceden tanımlanmış 3 tane standart akım mevcuttur. Bunlar; TextWriter türü olan Console.Out, Console.Error ve TextReader olan Console.In‘dir. Console ekranına yazdığımız veriler aslında TextWriter sınıfının metotları ile olmaktadır.

Sürekli kullandığımız Console.Writeline() metodu aslında bize bir aracılık akımı yapar. Yani aşağıdaki her iki ifade de doğrudur.

Konsol’dan bilgi almak için Console sınıfının Read() ve ReadLine() metodundan yararlanırız. Read() metodu okunacak akımdan bir karakter okur ve bu karakterin int değerini döner. Eğer akımdan veri okunmaz ise -1 değerini döner. Read() metodu biz enter tuşuna bastığımız anda girdiğimiz karakterleri akıma alır. Bu karakterler içinde “\n” ve ” \r” gibi karakterler de olabilir. Read() metodu tampon bölgeden okuma yaptığı için bu karakter okunmadan tampondan silinmeler.

Read() metodunun bu zorlu işleminden kurtulamk için ReadLine() metodunu kullanırız. ReadLine() metodu biz enter tuşuna basana kadar akımdan veriyi okur ve string türü olarak geri döner. İşin güzel tarafı enter tuşundan dolayı tampondaki “\n” ve “\r” gibi karakterlerin tampondan silinmesidir.

Read() ve ReadLine() metotlarını kullanarak bir örnek yapalım

Standart Akımların Yönlendirilmesi

C#’ta bütün I/O işlemleri akımlar (stream) üzerine kurulu. Standart akımları başka akımlara yönlendirmek mümkün. Örneğin Console.In standart girdi akımını bir NetworkStream (ağ-akımı) akımına yönlendirirsek Console.Read() ve Console.ReadLine() metotları ile bu ağdan bilgiler okunabilir.

İşletim sistemi düzeyinde de standart akımları yönlendirmemiz mümkündür komut satırından “<” ve “>” gibi imleçler kullanarak ilgili akımları dosya akımlarına yönlendirmiş oluruz.

C# programlarımızda akımları yönlendirmek için Console sınıfının aşağıdaki metotları uygulanır.

Console.In akımını SetIn metodunu kullanarak dosya akımına yönlendirelim.

Yukarıki programı derledikten sonra sample.txt dosyasını açın (yok ise dizinde bir tane oluşturun) ve bir şeyler yazın. Daha sonra programı çalıştırdığınızda dosyaya yazdığın satırların ekrana yazdığını göreceksiniz.

Şimdi standart yazma ve okuma akımını dosya akımına yönlendiren uygulama yapalım

Yukarıdaki örnekte hem dosyadan okuma hem de dosyaya yazma akımını Console akımından Dosya akımına yönlendirdik. sample-in.txt dosyasına yazdığımız satırları programı çalıştırdığımızda sample-out.txt dosyasına yazdığını göreceksiniz. Console.SetError standart akımını Console.SetError() metodu ile dosya akımına yönlendirmek yine aynı yöntemle olacaktır.

Evet bir yazının daha sonuna geldik. Açıkçası I/O konularında biraz eksik olduğumu fark ettim ve bu bilgilerle şimdilik bilgi açığını kapattığıma inanıyorum 🙂

Bu yazımda C# programlama dilinde Temel I/O İşlemleri hakkında elde ettiğim bilgileri paylaşmaya çalıştım. Umarım faydalı olmuştur.

Bir sonraki yazımda görüşmek üzere 🙂

Kaynaklar:

Her Yönüyle C# 7.0, Sefer Algan, Pusula Yayincilik

https://docs.microsoft.com/tr-tr/dotnet/api/system.io?view=netframework-4.8

Leave a Comment