Merhaba direk bu sayfaya gelenler için belirteyim, bu yazı serinin beşinci yazısı. Önceki bölümler için
https://ujk-ujk.blogspot.com/2023/01/c-temelleri-1.html ve
https://ujk-ujk.blogspot.com/2023/02/c-temelleri-2.html ve
https://ujk-ujk.blogspot.com/2023/02/c-temelleri-3.html ve
https://ujk-ujk.blogspot.com/2023/02/c-temelleri-4.html
sayfalarına bakınız.
C# Bir Nesnenin Parametre Değeri Olarak Verilmesi
Yine örnek olarak bir Araç sınıfı tanımı ile başlayalım.
Araç sınıfının 2 özelliği var model ve renk. Diyelim bir metod tanımlayıp araç1 nesnesinin rengini değiştirmek istiyoruz. Bu metoda da araç1 nesnesini parametre olarak göndermek istiyoruz.
Programı çalıştırırsak araç1 nesnesinin renk özelliği değerinin değiştiğini görürüz.
Anladım basitmiş diyenlere benim hala tam anlayamadığım noktayı bir örnekle gösterelim.
ve sonuç
araç1 nesnesinin özellik değerini değiştirebilirken sayı değişkeni değerini değiştiremedik. Uzun açıklaması var ama kısaca bildiğimi anlatayım.
Bir metoda parametreler değer olarak (by value) ya da referans olarak (by reference) gönderilebilir. C# dilinde eğer bir şey belirtmezseniz parametreleri değer olarak kabul eder. Ama nesneler default olarak referans değerlerdir, bu yüzden metoda içlerinde sakladıkları değerler değil nesnenin kendisi gider. int bir değişkeni referans olarak göndermek için ayrıca belirtmemiz gerekir.
Gördüğümüz gibi hem metodun tanımında parametre öncesine hem de metodu çağırırken int değişkenin adından öncesine ref kelimesi ekleyerek derleyiciye değer değil referans gönderdiğimizi belirtiyoruz.
Referans anlamını biraz açarsak değişkenleri içlerinde değerler olan kutulara benzetmiştik. Değer gönderirken o kutunun içindeki değeri gönderiyoruz, ama referans gönderirken "bu kutu" diyerek kutuyu gösteriyoruz, metod da gidiyor kutu üzerinde direk eylemini yapıyor gibi düşünülebilir.
Basit bir eşittir işleminde bile bu referans olarak ve değer olarak meselesi iş nesnelere gelince kafa karıştırır. Diyelim aracımızın bir kopyasını oluşturmak istiyoruz.
bu kodu çalıştırırsak
araç1 de araç2 de aynı nesne oldular. Birinde yapılan işlem aynen diğerinde de gerçekleşiyor. Çünkü nesneler default olarak referans tipi değerler olduğundan ikisi de aynı nesneyi (aynı kutuyu) ifade ediyor. Bir nesneden kopya çıkartmak için şöyle bir metod yazabiliriz.
Metodda yeni bir Araç nesnesi üreterek onu geri dönüyoruz, böylece farklı nesneler elde ediyoruz. Şimdi kodu çalıştırırsak,
Nesnelerin kopyasını çıkarırken böyle biraz zahmet etmek gerekiyor.
C# Alt Sınıflarda Metod Tekrar Tanımlamaları (overriding)
Ana sınıf tanımında tanımlanmış bir metodun ondan üretilen alt sınıfta farklı bir eylem yapmasını istersek metodu tekrar tanımlarız. Diyelim bir Hayvan ana sınıfımız ve bundan üretilmiş Kedi ve Köpek adında alt sınıflarımız var.
Ana sınıfta bir metod tanımlayalım.
Şimdi bir kedi ve bir köpek nesnesi üretip kullanalım.
Nesneleri tanımlarken kısaltılmış kodu kullandım. Bu kodu çalıştırırsak,
Fakat biz biliyoruz ki kedi de köpek de brr demez. Alt sınıf tanımlarında bu metod tanımının üzerine yazmak için (yani değiştirmek için) aynı metod tanımını alt sınıf içinde de ama bu sefer başında override kelimesi kullanarak yaparız.
Fakat bunu demek yetmez çünkü sistem bize "sen ana sınıfta bu metodun üzerine yazılabilir demedin" şeklinde bir uyarı verir.
Ana sınıf içinde de bu metodu virtual kelimesi ile üzerine yazılabilir olduğunu belirtmemiz gerekir.
ve çalıştıralım
Bir başka yöntem de ana sınıfı abstract tanımlayıp metodu da abstract tanımlamak. Ancak abtract metod tanımında kod bloğu olmaz. Şöyle ki,
Ama genel eğilim virtual kelimesi kullanmaktır. Alt sınıfta metodun üzerine yazılmazsa ana sınıftaki metod geçerli olacaktır. Bir örnek olarak sınıf tanımlarımızda yapmadığımız ama sistem tarafından sınıflara otomatik eklenen ToString() metodu da üzerine yazılabilen bir metoddur ve istersek bu metodun yaptığı işi değiştirebiliriz.
Artık şunu da kullanabiliriz,
ya da kısaca
C# ToString() Metodu
ToString() metodu bütün sınıfların en büyük atası olan Object sınıfında tanımlanmış ve kalıtım yoluyla tüm üretilen sınıflarda da olacak olan bir metoddur. Temsil ettiği nesneyi yazılabilir bir String değer ile ifade etme görevi vardır. Örnek olarak eskilere dönelim, Araç adında bir sınıf tanımımız olsun.
Ana programda bir araç nesnesi üretip ToString() metodu ile nesnemizi konsola yazdıralım.
Console.WriteLine() metoduna bir nesneyi parametre olarak verirsek o nesnenin ToString() metodunun çıktısını yazar. Bu kodu çalıştırınca
Nesnemizin veri tipini elde ettik, çünkü default bu şekilde çalışıyor. Şimdi ToString() metodu üzerine yazarak tekrar tanımlayalım.
Ya da kısaca
diyebiliriz. Çünkü üretici metod tanımındaki gibi aynı isimde başka değişken olmayınca this kelimesi ile ayrıca belirtmeye gerek kalmaz. Şimdi çalıştıralım
Gördüğümüz gibi Console.WriteLine() metodu her değer için akıllı bir nasıl yazacağı algoritmasına falan sahip değil. O nesnenin ToString() metodunu çağırıyor ve ondan dönen değeri yazıyor. Ben de sandım içinde double için ayrı, int için ayrı vs rutinler var, meğer bu kadar basitmiş.
C# Polymorphism (çok şekillilik)
Buna bir Türkçe kelimeyi tam yapıştıramadım. Bir sınıf birden fazla sınıfın özelliklerini taşıyabilir. Gerçek hayattan örnek vermek gerekirse, bir Köpek aynı anda hem Hayvan, hem Kaniş hem de Organizma sınıflarının özelliklerine sahip olabilir. Daha önce bir Araç ana sınıfından alt sınıf olarak Otomobil , Bisiklet ve Tekne alt sınıfları üretmiştik.
Farz edelim bir yarış düzenleyeceğiz ve her 3 türden de bir aracımız var.
Bu 3 nesneyi tek bir array içinde yarışa katılanlar olarak ifade etmek istersek, hepsini Otomobil türünde bir array ile gösteremeyiz hata verecektir.
Ancak polymorphism tekniğine göre hepsinin ortak olarak üretildiği Araç sınıfından yararlanarak bu araçları aynı array içinde toplamamız mümkün olur.
Şeklinde yazarsak sorun ortadan kalkacaktır. Farklı sınıfları birleştirmek için ortak olan taraflarını kullanabiliriz. Elmalar ile armutları toplayamayız ama bir tabakta 5 elma ve 3 armut varsa tabakta 8 meyve var diyebiliriz.
Şimdi yarışa katılan her aracın Git() adında bir metodu olsun ve yarış başlatmak için bu metodları çağıralım.
Şimdi yarışı başlatmak için bir foreach döngüsü içinde bu metodları çağıralım.
Burada bir eksik var Araç ana sınıfı içinde Git() metodu tanımlamadığımız için derleyici ne yapmak istediğimizi anlayamaz. Ana metodda tanımlanan bir metodun alt sınıf tanımlarında nasıl üzerine tekrar tanımlanabileceğini görmüştük, virtual ve override kelimeleri kullanarak.
Artık sorun kalmadı, programı çalıştırabiliriz.
Hiç bir ortak noktaları olmasa bile tüm sınıflar Object sınıfınnın mirasçıları olduğu için
Şeklinde bir yazım da çalışır, ama zorunda olmadıkça kullanılmasını tavsiye etmem, ortak metodlar ve özellikler sorun olabilir.
C# Sınıflarda Arayüz Şeklinde Tanımlama (interface)
Bir sınıf tanımı yaparken arayüz şeklinde tanımlama bir nevi kontrat yapmak gibidir. Tanımlanan bir arayüz ondan üretilen alt sınıfların nelere sahip olması gerektiğini belirler. Alt sınıfların tanımında da bu sahip olunması gerekenlerin nasıl olduğu belirtilir.
Örnek olarak 3 sınıfımız var, Tavşan , Şahin ve Balık. 2 tane de arayüz tanımlayacağız av ve avcı. Arayüz tanımlarında interface kelimesi kullanılır ve arayüze isim verirken başına büyük I harfi konulması adettendir.
Bu arayüzler temelinde kurulan sınıflar bu arayüzler içinde belirtilen özellik ve metod tanımlarına sahip olmak zorundadır. Mesela IAv arayüzünde bir sınıfın av olduğu için avcıdan kaçması için Kaç() adında bir metodu olmasını istiyoruz.
Sadece tanımlıyoruz ama kod bloğunu girmiyoruz. Bu arayüzü kullanan Tavşan sınıfımızı tanımlarsak bu metodun tanımı ve kodunu yazmak zorundayız. Arayüzü kullanan sınıf üretirken aynı ana sınıftan alt sınıf tanımlar gibi iki nokta üst üste işaretinden yararlanırız.
Şimdi bir tavşan nesnesi üretip kullanabiliriz.
Avcı arayüzümüzde de Avla() adında bir metod zorunluluğu getirelim.
IAvcı arayüzünden üreteceğimiz Şahin sınıfı tanımında Avla() metodunu tanımlamak zorundayız.
Şimdi Şahin sınıfından da bir nesne üretip Avla() metodunu kullanalım.
çalıştırırsak,
Okey.
Arayüz kullandığımızda birden fazla arayüzün kurallarına zorlanan bir sınıf da tanımlayabiliriz. Mesela Balık sınıfı hem av hem avcı olabilir. Bu durumda hem IAv hem de IAvcı arayüzlerinde belirtilenlere sahip olması gerekir. Birden fazla arayüz kullanan sınıflarda iki nokta üst üste işaretinden sonra arayüzlerin isimlerini aralarına virgül koyarak gireriz.
Her 2 arayüzde dayatılan metodları tanımlamak zorundayız. Hadi bir Balık nesnesi oluşturup kullanalım.
çalıştıralım,
C# Liste Kullanmak
Sınıf yapılarında çok dolaştık biraz veri yapılarına geri dönelim. List yapılar içlerinde nesne dizileri barındıran veri saklama yapılarıdır. Çalışma olarak biraz array yapılara benzerler. Benzerlik ve farklarını şimdi göreceğiz. array'lerden en önemli farkı program çalışırken boyutları (yani içlerinde barındırdıkları eleman sayısı) arttırılabilir ya da azaltılabilir. Hatırlarsak array'ler sabit eleman sayısında oluyordu.
Hatırlamak için bir örnek verelim.
Programda yemekler array'ine yeni bir eleman eklemeye kalkarsak, mesela,
Bu satır derlemede hata vermez ama programı çalıştırırsak çakılır ve IndexOutOfRangeException istisnai durumu oluşur. Çünkü array ilk tanımlanırken boyutu 3 olarak verildi ama 4üncü bir eleman değeri vermeye çalışıyoruz.
Yazacağımız programlarda verilerimiz program çalıştıkça artar ya da azalır. Bu durumlarda array yapısı pek işimizi görmez. Bu durumlarda List kullanarak işimizi hallederiz.
List kullanmaya başlamadan önce derleyiciye List yapısını da içinde barındıran System.Collections.Generic kütüphanesini de programımıza dahil etmesi için bir using satırını en üste eklemeliyiz.
Şimdi List yapısını programımızda kullanabiliriz. Az önceki örneği List ile yapalım. Tanımlama şöyle olacak,
List kelimesi ve hemen yanında küçük-büyük işaretleri arasında (aslında bu işaretler arasında olan şeye açılı parantez içinde denir) listemizin içerdiği nesnelerin sınıf adını yazıyoruz. Eşitliğin sağ tarafı da aynı bir sınıftan nesne üretirken yaptığımız gibi sonunda parantez aç-kapa ekliyoruz.
Daha önce nesne üretirken kullandığımız
şeklinde kısaltılmış yazım burada da geçerli tabi ki.
List'lere yeni eleman eklemek için sahip oldukları Add() metodunu kullanırız.
Add() metoduna parametre olarak listeye eklemek istediğimiz nesneyi veriyoruz. Örneğimizde elemanlar String olduğu için Add() metoduna da parametre olarak String değerlerimizi veriyoruz. Bir çalıştırıp görelim.
foreach döngüsü aynı array'lerde olduğu gibi çalışıyor, döngü kodunu aynen kullanıyoruz. Artık program çalışması esnasında yemekler listesine yeni eleman ekleyebiliriz.
Elemanlardan herhangi birine erişmek aynı array'lerde olduğu gibi köşeli parantez içinde index numarası vererek yapılır. Add() metodunu her kullandığımızda eklenen eleman listenin sonuna eklenir. Buna göre mesela
dediğimizde konsola "Hotdog" değeri yazılacaktır. Aynı notasyon ile değer de değiştirilebilir.
List sınıfının metodlarından biri de Remove() metodudur ve bir elemanı listeden silmek için kullanılır.. Parametre olarak silmek istediğimiz değeri veririz.
çalıştırırsak
Index 2'deki Hotdog değeri silinince Index 3'te olan Patates Kızartması değeri Index 2'ye geçer. Yani silinen elemanın yarattığı boşluğa ondan sonraki elemanlar kayarak liste küçülür. Remove() metodu taramaya Index sıfırdan başlar ve ilk bulduğu eşleşmedeki elemanı siler. Mesela 2 tane elemanın değeri Hotdog olsun,
Bu kodu çalıştırırsak Remove() metodu ilk eşleşmeyi silecektir.
İstediğimiz index'te bir eleman eklemek istersek Insert() metodu ile yaparız.
Bu kod index sıfır yani en başa Suşi değerini ekler.
Insert() metodu kullanıldığında verilen index değeri ve daha büyük index değerli tüm elemanların index değerleri bir artacak ve böylece kaydırılacaktır.
List'teki eleman sayısını öğrenmek için Count özelliğini kullanabiliriz.
satırı konsola yemekler listesinin eleman sayısı olan 4 yazacaktır.
Bir değerin List içinde hangi index değerinde olduğunu bilmek için IndexOf() metodunu kullanırız.
satırı bize Hotdog değerinin index'i olan 2'yi verecektir. Ama olmayan bir değer sorgularsak, mesela:
satırı listede bu değeri bulamadığı için -1 değeri dönecektir. IndexOf() metodu index sıfırdan başlayıp bize ilk eşleşen değerin index'ini verir. Sonuncuyu bulmak için de LastIndexOf() metodu vardır,
gibi.
Bir değerin listede olup olmadığını test etmek için Contains() metodu kullanılır. true ya da false değer döner.
satırı konsola True yazacaktır.
Listedekileri sıralamak için Sort() metodu kullanırız.
satırı listedeki elemanları harf sırasına göre sıralar.
Default olarak String değerler a dan z ye, sayılar 0 dan 9 a, tarihler eskiden yeniye sıralanır. Sıralama kriterleri ayrıca belirtilebilir ancak bu daha ileri bir kurs konusu.
List elemanlarının sırasını ters çevirmek için Reverse() metodu kullanılır.
satırını çalıştırırsak,
Tüm elemanları silmek için Clear() metodu kullanılır.
tüm liste elemanlarını silecektir. İlk tanımladığımız andaki gibi boş bir liste olacaktır.
Son örnek olarak List elemanı array elemana dönüştürebiliriz.
ToArray() metodu List türünü array türüne dönüştürmek için kullanılır.
C# Elemanları Kullanıcı Tanımlı Nesneler Olan List
Listeleri kendi tanımladığımız sınıflardan oluşturulan nesnelerin koleksiyonu olarak da kullanabiliriz. Örnek olarak bir oyunumuz olduğunu ve katılan oyuncuların bilgilerini bir listede toplamak amacıyla bir Oyuncu sınıfı tanımladığımızı düşünelim.
Şimdi bir liste oluşturup oyuncuları üretelim.
ve bu oyuncuları listeye ekleyelim
bir foreach döngüsü ile katılan oyuncuların isimlerini konsola yazdıralım.
dilersek Oyuncu sınıfı ToString() metoduna müdahale ederek default olarak oyuncu adının yazılmasını sağlayabiliriz.
artık konsola nesneyi direk yazdırırsak ismini yazacaktır.
Oyuncu nesnelerini tek tek tanımlamaya gerek yok listeye eklerken anonim şekilde yeni nesne üreterek de ekleyebiliriz.
Bu kadar. Kendi tanımladığımız sınıftan nesnelerle oluşturulmuş bir liste için yapmamız gereken açılı parantez içine sınıf adımızı yazmak.
C# Sınıf Tanımı get - set İşlemleri (getters and setters)
Sınıf tanımlarımızda özellik değerleri girerken bazen değerin belli kurallara uymasını isteriz. Örneğin bir Araç sınıfımız ve hız özelliği olsun.
hız değeri olarak integer olduğu için çok afaki değerler hatta eksi değerler bile verilebilir. Mesela yanlışlıkla
Yok daha neler ama bu değer geçerli bir değer, bir şekilde olası olmayan değer girişini engellemeliyiz.
İkinci satırdaki nesnenin özelliğine direk erişimi kapatabiliriz.
hız özelliğini private olarak tanımladığımız için sadece sınıf içindeki metodlar ona erişebilir. Ama hala ilk nesne üretimindeki yapılacak yanlış duruyor. Mesela,
yazılabilir. Bunu veya benzer kısıtlamaları yapabilmek için özellik değerlerine erişimde araya girip işlem yapan get ve set metodları kullanırız.
Biz şimdiye kadar sınıf özellikleri belirlerken hep sınıf içinde geçerli değişkenler tanımladık aslında. Esas olarak bir sınıf tanımında özellik belirtmesi metod tanımlamasına benzer ve bu tanımlama içinde özellik değeri okunurken ve değere yazılırken ayrı işlem blokları belirtilir. Özellik değeri okunmak istendiğinde get bloğu çalışır ve bir değer döner. Özellik değeri değiştirilmek istendiğinde ise set bloğu çalışır ve dışarıdan gönderilen değeri işleyerek özelliğe yazar.
Bir de özellik isimleri büyük harfle başlar. Şimdi hız değerimiz için bir özellik tanımlaması yapalım.
Metod tanımlar gibi başlıyoruz ama özellik adı olan Hız kelimesinden sonra parantezler yok. Bu durumda bu bir sınıf özelliği olarak değerlendirilir. Bu aslında default özellik tanımlama şekli. get ve set blokları da aslında özelliğin değerini okurken ya da yazarken kullanılan bloklardır ve dikkat edersek sadece isimleri ve arkasından blokları var parantez falan yok. Bunlar aslında metod değil setter ve getter blokları olarak bilinir. Şöyle biraz daha topluca gösterelim,
Önce sınıf içinde dışarıdan erişilemeyecek hız değişkenini private olarak tanımlıyoruz. Hız özelliğini ise standart sınıf özelliği olarak tanımlıyoruz. Hız değerine ulaşmak isteyen her kod eğer Hız değerini okumak istiyorsa get bloğunu , eğer Hız özelliğine bir değer vermek istiyorsa set bloğunu otomatik olarak çağıracaktır.
Hatta ne yaptık sınıf üretici metodunda?
private olan hız değişkenine yazmıyoruz özellik olan Hız değerine yazıyoruz. Bu demektir ki yeni nesne üretirken de set bloğu çalışacak.
Nasıl çalışıyor kısaca bakalım,
Burada deniyor ki Hız özelliği değeri okunmak istendiğinde nesne içinde kullandığım hız değişkeni değerini gönder.
Burada da deniyor ki dışarıdan Hız özelliğine değer girilmek istenirse o girilen değeri hız dahili değişkenine yaz. value kelimesi burada dışarıdan girilen değeri ifade eder. Yani
yazdığımızda value değeri 120 olarak set bloğu işletilir.
yazdığımızda da value değeri 100 olarak set bloğu işletilir.
get ve set bloklarında şu anda hiç bir işlem yapmıyoruz. Sadece standart bir sınıf özelliği tanımı yaptık. Eğer özel bir işlem yapmayacaksak blokları ayrıca belirtmeye gerek yok. Kısaca şöyle yazılabilir.
Ama biz değer girerken işlemler yapacağız , bu yüzden bu notasyonu kullanmayacağız.
Devam etmeden bir konuya daha değineyim. Özellik tanım bloğunda sadece get işlem bloğu tanımlarsak set bloğu tanımlamazsak (ve set kelimesini hiç kullanmazsak), bu özelliğin sadece okunabilir olduğunu dışarıdan değiştirilemeyeceğini gösterir. Neye yarar derseniz String değerler için olan Length özelliğini gösterebilirim. Bu değer sadece okunabilir ve String değerin kaç karakter uzunlukta olduğunu bize bildirir. Yani Length değeri okunmaya çalışılınca sınıf tanımı içindeki get bloğu String değerin kaç karakter olduğunu hesaplayıp bize döner.
Örneğimize dönelim. Hız değeri girilirken maksimum girilebilecek değeri 500 olarak sınırlamak istersek,
if bloklarında tek satır olduğu için süslü parantez içinde göstermeye gerek yok kısaca,
yazabiliriz ya da üçlü operatör ile
Bunları unutmayın sık sık kullanın ki kod görünümü daha basit olsun.
C# get - set Erişimcilerinin (accessor) Kısa Kullanımı
Bundan sonra sınıf tanımlarında bir özellik belirtirken öyle public değişken kullanmak yok , standart yapıları kullanacağız. Yukarıda kısaca get - set erişimcilerinin kısa kullanımından bahsettik. Şimdi bir örnekle bilgimizi pekiştirelim. Bir Araç sınıfı tanımımız ve Model özelliği olsun,
get ve set erişimcileri içinde standart dışı bir şey yapmayacaksak bu kodu çok daha kısa yazabiliriz.
Bu kod yukarıdaki ile aynı işi yapar ama çok daha kısa. Kısa bir programla test edelim.
ve çalıştıralım
Sıkıntı yok. Model özelliğinin yalnızca okunabilir olmasını istersek,
yazarız ve set erişimcisi olmadığı için Model özellik değeri değiştirilemeyecektir. Ancak programı tekrar çalıştırırsak hala sağlıklı çalıştığını görürüz. Bunun sebebi sınıf üretici metodunun tanımı yapılmasa da özellik set edebilmesidir. Ama dışardan nesne özelliğini değiştirmeye kalkarsak , mesela
Son satır hata verecek ve Model özelliğinin salt okunur bir özellik olduğunu belirtecektir. Yani sadece yeni bir nesne üretirken Model girmek zorundayız ve sonradan bu değer sadece okunabilir, değiştirilemez.
C# enum kullanımı (enumerations)
enum özel bir sınıf tanımıdır. İçinde isimlendirilmiş sabit tamsayı değerler barındırır. Bir şeyleri sayılarla ifade edeceksek , mesela mezuniyet bilgisi, 1:okumamış, 2:ilkokul, 3:ortaokul, 4:lise vs. Daha sonradan 1 neydi 2 neydi diye hatırlamaya çalışmak yerine kullanılabilecek yapılardır. Bir örnekle açıklarsak daha anlaşılır olacak. Gezegen isimlerini içeren bir enum tanımlaması yapalım.
İsimlerin bir String gibi tırnak içinde olmadığına dikkat edin. Sınıf tanımı gibi enum tanımlarını da çalıştığımız sınıfın dışında ama namespace içinde yapıyoruz. Özellikle belirtilmediği sürece ilk elemanın (Merkür) karşılığı olan tamsayı sıfır sonraki 1 vs şeklinde gider. Şimdi bu enum değerleri kullanalım
ve sonuç
Şimdi bunu bir array içinde yazmış olsak
Gibi bir şey yazmamız gerekecek ve acaba Plüton 7 miydi? 9 muydu? falan deyip her seferinde tanımın yapıldığı yere dönecektik halbuki Gezegenler kelimesi yanına noktayı koyduğumuz anda editör bize hemen tüm değerleri gösterip yardımcı oluyor. Değerlerin tamsayı karşılıklarını kullanmak ise ileride enum yapıları kullanırken çok gerekecek bir şey. Tamsayı karşılık gelen değerleri almak için basit yerinde dönüştürme yaparız. Örnek
ve çıktısı,
Biz bu otomatik numaraları sevmedik kendi sayılarımızı girelim dersek,
Şeklinde kendi istediğimiz numaraları da verebiliriz. Şimdi çalıştırırsak
Bu sayılar bambaşka olabilir mesela gezegenlerin çapları yazıyor olabilir.
Şimdi bu değerleri kullanalım
En üstte enum değerin ToString() metodunun kullanıldığına dikkat edelim. Bu değerler String değil konsola yazdırırken otomatik olarak ToString() metodu çağrılıyor, ama String bir değişkene değer vereceksek ToString() metodunu kendimiz çağırmalıyız.
Gördüğümüz üzere hatırlamakta zorlanacağımız sayıları da böyle isimlendirebiliriz. Örneğimizi geliştirelim ve bir gezegenin hacmini hesaplayan bir metod yazalım.
çap değerinin küpünü almadan önce tamsayı karşılığı için int değere çevirmeliyiz. Dikkat edersek (4/3) değil (4.0/3.0) yazdık. Noktalı şekilde yazmazsak parantez içini hesaplarken tamsayı olarak bölme yapacaktı, ayrıca belirtmek lazım. Şimdi bu metodu da kullanalım,
C# Jenerik Değerler (generic)
Jenerik değerler tek bir tür veriye bağlı olmayan içlerinde değişik veri tiplerini barındırabilen değerlerdir. Jenerik sınıflar, jenerik metodlar ve jenerik sınıf özellikleri olabilir. İsimlere açılı parantez içinde büyük T harfi ekleyerek (<T>) ifade edilirler. 3 değişik veri tipinde elemanlar barındıran array tanımları ile örnek verelim.
Bu array'lerin eleman değerlerini konsola yazdıracak bir metod tanımlamak isteyelim. Örneğin int değerler için şöyle yazarız,
Bu metodu intArray değişkeni için kullanabiliriz.
Fakat aynı metodu double ve String değerli elemanlar içeren array'ler için kullanamayız. Şimdiye kadar öğrendiklerimize bakarsak tamamen yeni metodları bu sefer double ya da String değerleri işleyecek şekilde tanımlamak zorundayız.
Aynı isimde metodlar tanımlıyarak bunu yapabileceğimizi gördük.
Değişik tip parametreler ile çalışabilen 3 metod tanımı yaptık bir-iki fark dışında neredeyse aynı kodlar var. Bu kodu jenerik değerler kabul eden bir metod tanımı ile kısaltabiliriz.
Notasyondaki T harflerine dikkat edelim. Burada T harfi kullanma zorunluluğu yok, yeterki her kullanıldığı yere aynı şeyi yazalım. Yani
şeklinde de yazabiliriz. Metodu int değerler ile çağırınca o bişey yazısı yerine otomatik olarak int gelir ve metod ona göre çalışır, double değerler ile çağrılınca bişey yazılan yerlerde double yazıyormuş gibi işlem yapılır vs. Artık tek bir metod tanımı birçok veri tipini kabul edebiliyor. Sadece örnekteki veri tipleri değil array olan her veriyi kabul eder. Mesela
Bu da çalışacaktır. Tek bir metod bir çok veri tipini kabul ediyor ve çalışıyor.
C# Çoklu İş Parçacıkları (multi thread)
Bazen programımız bir olayların oluşmasını beklerken paralelde başka işleri yapmasını isteriz. Bu en çok zamanlanmış işler ve dışarıdan bir veri ya da etkileşim beklerken ihtiyaç duyduğumuz bir şeydir. Mesela dakikada bir yapacağımız bir iş var ve saniyede bir yapacağımız başka bir iş var. Biz dakikada bir yapılacak iş için program dakikanın bitmesini beklerken saniyede bir yapılması gerekenleri yapmaya devam etmesini isteriz. Bunu kodlar , döngüler vs ile halledebiliriz ama işler çoğaldıkça kodumuz çorbaya dönecektir.
Bu sorunları çözmek için C# iş parçacıkları (thread) tanımlamayı ve birden çok iş parçacığının paralel çalışmasını kullanmamıza imkan sunar.
Aslında şimdiye kadar kullandığımız Main() metodu da bir iş parçacığıdır ve Main thread olarak adlandırılır. Yani şimdiye kadar yaptığımız örneklerde sadece tek bir iş parçacığı vardı o da Main bloğu.
İş parçacıklarını kullanmak için önce en üstte bir using yönergesi ile belirtmeliyiz.
Örnek olarak içinde bulunduğumuz iş parçacığını algılayıp şöyle isimlendirebiliriz.
Thread.CurrentThread özelliği bize programın o anda içinde bulunduğu iş parçacığı nesnesini verir. Daha sonra bu iş parçacığının Name özelliğine değer giriyor ve konsolda yazdırıyoruz.
Çoklu iş parçacıklarına örnek olarak paralel çalışan 2 sayaç yapacağız biri sıfırdan 10'a yukarı doğru sayarken diğeri 10'dan sıfıra aşağı doğru sayacak. Öncelikle 2 metod tanımlayacağız ilki yukarı doğru sayan bir metod.
Burada yeni olan tek şey Thread.Sleep(1000) satırı. Bu komut milisaniye olarak yani 1000 ms = 1 saniye o satırda bekliyor. Böylece metodun saniyede bir sayı sayması sağlanıyor. Aslında bekleme 1000 ms , konsola yazma ve döngüye gidip tekrar gelme zaman gecikmeleri de 2 ms desek 1002 ms de bir kod döngü yapar. Ama burada derdimiz bu küçük hata değil. "Mühendislik ihmaller sanatıdır" derdi hocalarımız. İşimi engellemeyecek kadar küçük yanlışları yok sayıp devam ederiz yolumuza.
Bu metodu ana program içinde kullanırsak sağlıklı saydığını görürüz.
Şimdi bir de aşağı sayan metod yazalım
Bu metodu da ana program içinde kullanalım.
Bu kodu çalıştırırsak önce sıfırdan 10'a kadar sayacak ve "Yukarı sayım bitti." yazacak , sonra 10'dan sıfıra aşağı doğru sayacak ve "Aşağı sayım bitti." yazacak en son da "Ana iş parçacığı tamamlandı" yazacaktır.
Ancak biz sayaçların aynı anda çalışmalarını istiyoruz. Bunu sağlamak için bu 2 metodu başka başka iş parçacıkları içinde çalıştırmalıyız. Bunu yapmak için 2 yeni iş parçacığı (Thread) üretiriz ve bunlara metodlarımızı işlemelerini emrederiz.
İş parçacıklarının çalıştıracağı metodların ismini verirken parantez kullanmıyoruz sadece metod ismini yazıyoruz dikkat edelim. Eğer parantez kullanmaya kalkarsak program o ana gelince metodu çalıştırır ve metoddan dönen ne varsa onu parametre olarak new Thread() metoduna gönderir, halbuki biz metodu komple iş parçacığına gönderiyoruz. Böylece thread1.Start() ile iş parçacığı çalıştırılınca bizim metodumuz çalıştırılacaktır.
Bu kodu çalıştırınca şu cevabı görürüz.
Neler oluyor kod üzerinden anlayalım. Anlamaya çalışırken şunu unutmayalım kodumuzu işleyen bir işlemci var. Bunu bir görevlinin bizim satırlarımıza bakıp orada denilenleri yaptığını düşünerek hayal edebiliriz. Önce thread1 iş parçacığı tanımlanıyor ve yukarıSay metodu ona iliştiriliyor. Bu sadece bir tanımlama bu satırda metod çalıştırılmaz. Sonra aynı şekilde thread2 iş parçacığı tanımlanıyor ve ona da aşağıSay metodu iliştiriliyor.
thread1.Start() komutunu görünce işlemci bulunulan iş parçacığından çıkar (mainThread iş parçacığından çıkar) ve thread1 iş parçacığında verilen yukarıSay metodunu çalıştırmaya başlar. Bu metod ilk satırını yazar ve Thread.Sleep(1000) satırına gelince metod bitmemiş olduğu halde iş parçacığı geçici olarak bırakılıp ana koda geri döner.
Sırada thread2.Start() satırı var. Bu sefer işlemci bulunulan iş parçacığından çıkar (mainThread iş parçacığından çıkar) ve thread2 iş parçacığında verilen aşağıSay metodunu çalıştırmaya başlar. Bu metod da ilk satırını yazar ve Thread.Sleep(1000) satırına gelince metod bitmemiş olduğu halde iş parçacığı geçici olarak bırakılıp ana koda geri döner.
Şimdi sırada Console.WriteLine(mainThread.Name + " tamamlandı") satırı vardır ve konsola "Ana iş parçacığı tamamlandı" yazar ve Console.ReadKey() satırına gelir. Buraya geldiğimize göre bir tuşa basarsak program sonlanacak diye düşünürüz ama içinde bulunduğumuz mainThread iş parçacığının bitmesi için onun içinde başlatılan iş parçacıklarının sonlanmasını bekler.
Bu yüzden işlemci sırayla thread1 ve thread2 iş parçacıklarına dönerek kontrol yapar. Ne zaman ki Thread.Sleep(1000) ile beklenen 1000 ms dolar döngü sonraki adıma geçer. Bu işlem bir ona bir diğerine geçerek döngüler bitene ve bitiş mesajları da yazılıp metodlar sonlanana kadar devam eder. yukarıSay metodu çalıştırması bitince thread1 iş parçacığı sonlanır. aşağıSay metodu çalıştırması bitince de thread2 iş parçacığı sonlanır. İçinde başlatılan her 2 iş parçacığı da bitince mainThread iş parçacığı da artık bitebilir demektir.
Denemek isterseniz sayma işlemi devam ederken bir tuşa basın. Sistemin tuşu aldığını fakat saymanın devam ettiğini göreceksiniz. Sayma işlemleri bittikten sonra önceden basmış olduğunuz tuş olduğu için program beklemeden hemen sonlanacaktır.
Metodlar parametreli ise parantez kullanmadan nasıl çağıracağız? Sorusu akla takılmazsa olmaz. Bu durumda özel bir notasyon kullanacağız. Diyelim metodlarımız parametre olarak isim değeri istiyorlar.
Thread nesnesi tanımlamamız şöyle olmalıdır.
() => yukarıSay("Sayaç 1") komutu lambda operatör ile işlem tanımlama olarak bilinir..Şöyle düşünebiliriz, parametresi olmayan bir metod tanımlıyoruz ama o metod içinde yukarıSay("Sayaç 1") metodunu parametre ile çağırıyoruz.
Biraz daha açalım
şeklinde bir metod tanımlayıp bunu yukarıda
şeklinde kullansak program çalışır. Lambda notasyonu ile metodu tanımlasak
Şeklinde tanımlarız ve bu da çalışacaktır.Hiç metod tanımlamayıp direk parametre yerine
yazdığımızda da çalışacaktır.
Bu yazı dizisi burada bitiyor. Yeni başlayacak olanlar ve başlangıç seviyeden biraz orta seviyelere çıkmak isteyenler için yaralı olacağını düşünerek bu yazı dizisini yazdım Umarım işinize yarar.
Sonraki yazılarda buluşmak ümidiyle kalın sağlıcakla..
Hiç yorum yok:
Yorum Gönder