Kendi eğitim notlarımı biraz toparlayarak burada paylaşmaya karar verdim. Uzun bir yazı olacak ama temek kullanımlar hakkında referans bilgi teşkil edebilmesi amacıyla tüm konuları içinde bulunduracak bir referans olacak. İleri seviye konular arasında eksik olanları da zaman buldukça örneklendirerek paylaşmayı umuyorum.
C# programlama dilindeki tüm teorik bilgileri özet haline kendime hazırlarken biraz daha özenerek burada paylaşılabilir hale getirmek istedim. Konuları örneklerle zenginleştirerek ana hatlarıyla tüm konular hakkında tam bir kılavuz olarak sizlerle paylaşıyorum. Zaman içerisinde içerisindeki konular hakkında detaylı örnekler de hazırlayarak ilgili yerlere link vererek bu makaleyi aktif ve güncel tutmayı planlıyorum.
Notlar derlenirken çoğunlukla C# teorisi üzerinde durulmuştur, zaman zaman da .NET kütüphanesi kullanılarak örneklere yer verilmiştir.
C# ile .NET arasındaki fark nedir?
C# bir programlama dilidir, .NET ise bir framework’tür. .NET sadece C# ile kullanılmalıdır gibi bir sınırlama yoktur, farklı programlama dilleri (F# ya da VB.NET gibi) ile birlikte kullanılabilir.
.NET framework temel olarak iki bileşenden meydana gelmektedir:
1. CLR (Common Language Runtime)
2. Class Library (Sınıf Kütüphanesi)
CLR’ın ne işe yaradığını anlamak için, C# öncesi kullanılan C ve C++ dillerinin işleyişlerini biraz anlatmakta fayda var. C ve C++ dillerinde uygulamamızı derlediğimizde Compiler (derleyici), kodlarımızı makine dilinde kodlara dönüştürmekteydi. Bu da yazılan uygulamaların işletim sistemi ve işlemcilere bağımlı olması anlamına geliyordu ve örneğin Windows işletim sisteminde hazırlanan bir C++ uygulamasının Linux ortamında çalışamamasına yol açıyordu. C# ve .NET framework ise, önceden Java’da başarıyla uygulanan ara katman mantığını kullanarak derlenmiştir. Bu IL (Intermediate Language) kod adı verilen katman sayesinde uygulamalar, hazırlandıkları cihazın özelliklerinden bağımsız olarak hazırlanabilir ve çalıştırılabilir hale gelmişlerdir. IL kod tarafından makine diline dönüştürülen kodlar, farklı işlemci ve işletim sistemlerinde de aynı uygulamaları kullanabilmemizi sağlamaktadır. .NET framework’te bu görevi yapan katmana CLR adı verilmektedir ve IL kodunu makine koduna çevirme işini yapmaktadır. Bu işleme de JIT (Just in time Compilation) adı verilmektedir.
.NET uygulamaları, sınıflar içerisinde sınıflardan oluşan birer sınıf kütüphaneleridir. İçerisinde veri (data) ve fonksiyonların (metod) olduğu alanlara şimdilik class (sınıf) diyebiliriz. Sınıfa bir örnek vermek için bir arabayı düşünebiliriz. Araba sınıfının özellikleri:
- Markası
- Modeli
- Rengi
- Üretim yılı
- Şanzıman türü
Olabilir. Benzer şekilde araba sınıfının metodları da:
- Çalıştır()
- Sür()
Gibi düşünebilir.
Uygulamalarda çeştli işlevleri yerine getiren ilişkili ya da ilişkisiz çok sayıda sınıf ile çalışmamız gerekir. İşte bu birden fazla ilgili sınıfı kapsayan alana namespace adı verilir. Veriler, görseller, matematiksel işlemler, güvenlik gibi çeşitli alanlarda class’lar ve namespace’ler kullanmak durumunda kalırız. Birden fazla namespace’i kapsayan alana da assembly (dll [dynamic link library - kütüphane] ya da exe [executable - çalıştırılabilir] ) adı verilir. Bir ya da birden fazla assembly kullanarak hazırlanmış yazılım parçasına da uygulama adı verilir.
C# Dilinde Değişkenler ve Sabitler
- Bellekte yer kaplayan ve içerisinde değer saklamaya yarayan her bir depolamaya değişken adı verilir.
- Değişmez değerlere ise sabit adı verilir. Derleme zamanından itibaren değeri bilinir ve uygulamanın çalışması süresince de değeri değişmez. Uygulamanın güvenliği için gereklidirler. Örneğin matematiksel işlemlerde kullandığımız pi sayısı bu sabitlere örnek olarak verilebilir ve değeri her zaman 3.14’tür. Pi sayısını programın herhangi bir noktasında değiştirmemiz güvenlik hatası olarak ifade edilir, bu yüzden değiştirilmesine izin verilmez. const anahtar sözcüğü ile sabit değerler belirtilebilirler.
C# büyük ve küçük harfe duyarlı bir dil olduğu için küçük ve büyük harflerin kullanımına dikkat edilmelidir. Bu seveple sayi ve Sayi gibi iki aynı isme sahip değişken, harf büyüklüğü farklılığından dolayı farklı değişkenler olmuş olacaklardır.
int sayi;
int Sayi = 5;
const float pi_sayisi = 3.14f; //sabit
Değişken Tanımlamaya ilişkin Kurallar
- Değişken adları, sayı ile başlayamazlar. Örnek olarak birinciOyuncu bir değişken adı olabilir ancak 1Oyuncu ilk karakteri bir sayı olduğu için değişken adı olarak kullanılamaz.
- Değişken adlarında boşluk karakteri kullanılamaz. birinci oyuncu hatalı bir değişken adıdır, doğrusu birinciOyuncu olmalıdır.
- C# tarafından kullanımı önceden tanımlanmış (rezerve) anahtar kelimeler, değişken adı olarak kullanılamazlar. Örnek olarak int adında bir değişken tanımlaması yapmak mümkün değildir. Ancak başına @ karakteri konularak kullanılabilir hale getirilebilirler. (@int isminde bir değişken kullanılabilir)
- Değişken adlarının anlaşılabilir olması tercih edilmelidir. Örneğin bo adındaki bir değişkenin neyi temsil ettiğini bir süre sonra kendiniz bile hatırlamayabilirsiniz ancak birinciOyuncu çok daha anlaşılabilir bir değişken adıdır.
- C ailesinde değişken isimlendirmesi yapılırken üç farklı yaklaşım vardır. Bunlar:
Camel Case: birinciOyuncu
Pascal Case: BirinciOyuncu
Macar Notasyonu: strBirinciOyuncu
C# uygulamalarında genel olarak kullanımı kabul edilmiş yazım şekli değişkenler için Camel Case; sabitler için Pascal Case’dir. Farklı yazım şekilleri olması sayesinde bir değişkenin değerinin sabit olup olmadığı konusunda bilgi sahibi olunabilmektedir.
int sayi; //değişken
Const KapiSayisi = 4; //sabit
C# İlkel (Primitive) Veri Tipleri ve Sınırları
byte: 0-255
short: -32.768 – 32.767
int: -2.1Milyar – 2.1Milyar
long: ...
float: -3.4x10^38 – 3.4x10^38
double: ...
decimal: -7.9x10^28 – 7.9x10^28
char: Unicode karakterler
bool: Doğru/Yanlış
Overflow (Taşma) Nedir?
Peki bir veri tipinin sınırını aşarsak ne olur?
Örneğin int veri tipindeki bir değişkenin alabileceği en büyük değer olan 2147483647 değerine sahip olan bir sayi değişkenine +1 eklemek istersek bakalım sonuç ne oluyor?
int sayi = int.MaxValue; //2147483647
sayi = sayi + 1; //-2147483648
Değerin 1 artarak 2147483648 olması beklenirken int veri tipinin negatif kutupta alabildiği diğer en uç değer olan -2147483648 olması, değişkenlerin aslında birer sayı doğrusu olarak çalıştığını anlamamıza yardımcı olabilir. Bu sınırların aşımı işlemine de yazılım dilinde overflow adı verilmektedir.
Overflow Nasıl Önlenebilir?
Taşma işlemini engellemek için tasarlanan kontrol mekanizması, checked anahtar sözcüğü ile kullanılmaktadır. Örneğin az önceki örnekteki int (tamsayı) bir değişkendeki taşmayı önlemek için:
checked
{
int sayi = int.MaxValue;
sayi = sayi + 1;
}
Program bu şekilde derlenerek tekrar çalıştığında artık hatalı bir işlem yapılmayacak, bunun yerine bir hata mesajı ile programın çalışmasının durması sağlanacaktır.
Alınacak hata mesajı: Unhandled Exception: System.OverflowException: Arithmetic operation resulted in an overflow.
Bu sebeple, programlama mimarisini yaparken her bir değişkenin yükleneceği değerler önceden titizlikle düşünülmeli ve olası taşmalara ya da falz bellek kullanımlarına izin verilmemelidir.
Scope (Kapsam) Nedir?
Değişkenlerin kapsam alanlarının belirlenmesi işlemine programlama dillerinde Scope adı verilmektedir ve birbirlerinden süslü parantez “{}” karakterleri ile ayrılırlar.
Örnek olarak süslü parantezler ile ayrılmış olan a, b ve c değişkenlerinin kapsam alanına girip girmedikleri alanlar aşağıdaki görselden incelenebilir.
Burada c değişkeni, kendi parantezleri dışına çıkıldığında geçersiz olacaktır. (Sadece kırmızı alanda geçerli)
b değişkeni ise kendi yeşil alanı ve altındaki kırmızı alanda geçerli olacaktır
Son olarak a değişkeni ise hem mavi, hem yeşil, hem de kırmızı alanda tanımlıdır.
Visual Studio, tanımsız değişkenleri altlarını kırmızı ile çizerek gösterdiği için aşağıdaki görselden daha kolay anlamamızı sağlayacaktır.
C# ve .NET ile Konsol Uygulamaları Hazırlamak
Visual Studio’dan yeni bir konsol uygulaması açmak için (File -> New -> Project -> Visual C# -> Windows Desktop -> Console App (.NET Framework)) yolunu takip edebilirsiniz.
Kodları derlemek için Ctrl+Shift+B kısayolu kullanılır.
Kodlarınızı çalıştırmak için Ctrl+F5 tuşlarına aynı anda basmanız yeterlidir.
Kullanacağımız konsol komutlarının anlamları:
Console.Write(): Çıktı almaya yarayan konsol komutudur, parantez içerisindeki değer ya da değişkenleri çıktı olarak konsol ekranında görüntüler.
Console.WriteLine(): Çıktı alıp bir satır aşağıya atlamaya yarayan konsol komutudur.
//: tek satırlı yorum yazmaya yarayan komuttur ve yorumlar derleyici tarafından dikkate alınmayan her türlü yazı anlamına gelir. Birden çok satırlı yorum yazmak için /* ile */ arasındaki alan kullanılabilir. Yazdığımız kodları kısmen deaktive etmek istediğimizde de yorum özelliğini kullanabiliriz.
// bu tek satırlık bir yorumdur
/*
Buraya çok satırlı
yorum
yazabilirim
*/
Sayısal Konsol Örneği
Main metodu, bir programın nereden başlayacağını belirttiğimiz metoddur. Bu yüzden (en azından şimdilik) tüm kodlarımızı Program.cs altındaki Main metoduna yazacağız.
İlk örneğimizde bir tamsayı (int) değişken tanımlayarak bu değişkenin değerini çıktı alalım.
static void Main(string[] args)
{
int sayi;
Console.Write(sayi);
}
Çıktı: Error: CS0165 Use of unassigned local variable 'sayi'
İlk örneğimizde hata aldık, çünkü oluşturduğumuz sayi adındaki değişkene değer vermedik, haliyle derleyici de değeri olmayan bir değeri çıktı alamadığı için hata vermiş oldu. Çözümü için her değişkenin bir değeri olması gerektiğini anlamamız gerek.
static void Main(string[] args)
{
int sayi=5;
Console.WriteLine(sayi); //cikti=5
}
Kısayol: Console.WiteLine() yazmak için kısaca cw yazarak tab tuşuna basabilirsiniz, Visual Studio otomatik olarak Console.WiteLine() yazacak ve imleci de parantezin içerisine getirecektir.
Console.WriteLine ile çıktı almanın bir diğer yöntemi, bir ya da birden fazla çıktıyı sağdaki sıra ile süslü parantezlerin yerine yerleştirerek almaktır.
string adi = "adım";
string soyadi = "soyadım";
Console.WriteLine("{0} {1}", adi, soyadi); //çıktı: adım soyadım
Farklı veri türleri için farklı tanımlama biçimlendirmeleri vardır. Örnekleri aşağıdaki gibi inceleyebilirsiniz:
byte sayi = 3;
int buyukSayi = 354;
float fiyat = 19.95f;
double kdv = 19.95;
bool mesgul = false;
char karakter = 'f';
string isim = "Adı Göbekadı Soyadı";
Not: Veri tipi bilinmeyen ya da bilinse de kullanılması tercih edilmeyen durumlarda var değişken tipi belirteci de kullanılabilir:
var sayi = 3;
var buyukSayi = 354;
var fiyat = 19.95f;
var kdv = 19.95;
var mesgul = false;
var karakter = 'f';
var isim = "Adı Göbekadı Soyadı";
Sabit tanımlamak:
const float piSayisi = 3.14f;
piSayisi = 3.15f; //hata
piSayisi değişkeni const olarak tanımlandığı için bu kod derlenmeden altı kırmızı ile çizilerek hata verecektir, çünkü const değişkenler sabittir ve güvenlik gereği programın herhangi bir yerinde değiştirilemezler.
C# Tür Dönüşümleri
Bir veri tipindeki değişkeni farklı bir veri tipine dönüştürmek amacıyla kullanabileceğimiz üç çeşit tip dönüşümü vardır.
1. Kapalı dönüştürme (Implicit)
byte a = 1;
int b = a;
Bu örnekte 1 byte yer kaplayan a değişkeni 4 byte yere sahip olan b değişkeni içerisine kopyalanmaktadır. Bu işleme kapalı dönüştürme adı verilir. Peki ya 4 byte’lık bir değer 1 byte yere sahip bir byte veri tipindeki değişkenin içerisine kopyalanmak istenirse ne olur?
int a = 1;
byte b = a; //hata
Hata mesajı da şu şekilde olacaktır: CS0266 Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?
Olası veri kayıpları durumları için kapalı dönüştürme tekniklerine izin verilmemektedir. Bu işlem için açık dönüştürmeyi deneyelim.
2. Açık dönüştürme (explicit) (casting olarak da ifade edilir)
int a = 1;
byte b = (byte)a; //sonuç: 1
Bir eğerin ya da değişeknin sol tarafına parantez ile sahip olmadığı bir veri tipini yazmaya casting adı verilir ve casting bir açık veri tipi dönüştürme yöntemidir.
Küsüratlı float ya da double değişkenlerin küsüratlarının atılarak tamsayı kısımlarının alınmasında da zama zaman (int) casting yönteminden yardım alınmaktadır.
double tutar = 26.85;
int kusuratsiz = (int)tutar; //sonuç: 26
3. Uyumlu olmayan türler arası dönüşüm
Sayısal değişkenler arasında dönüşüm yapmak, birbirleri ile uyumlu veri türleri oldukları için bir derece mümkündü. Peki ya birbirleri ile uyumsuz olan string ile int gibi değişken türleri arasında dönüşüm yapmak istersek ne olacak?
string sayi = "1";
int hakikiSayi = (int)sayi; //hata
Birbirleri ile dönüşüm biçiminden uyumsuz olan veri türleri arasında açık dönüştürme yöntemleri de hata verirler. Bu tip durumlar için ise birbirleri ile uyumsuz yöntemlerin dönüştürülmesi amacıyla hazırlanmış yöntemleri kullanmamız gerekir:
string sayi = "1";
int hakikiSayi = Convert.ToInt32(sayi); //değer 1
ya da
string sayi = "1";
int hakikiSayi = int.Parse(sayi);//değer 1
Her iki yöntem de uyumlu olmayan türler arası dönüşüm için kullanılabilir. Convert sınıfının sayısal dönüşüm için kullanılabilecek yöntemleri:
. ToByte()
. ToInt16() //short değişkene çevirmek için
. ToInt32() // int’e çevirmek için
. ToInt64() // long’a çevirmek için
Not: Mouse kullanmadan sadece klavye ile Visual Studio tarafından anlaşılabilir komutlar verebilirsiniz. Örneğin çok sayıda kod yazıldığında aşağıya doğru birer satır silerek temizleme yapmak isterseniz mouse imlecinizin sağ tarafındaki kodları silecek şekilde ctrl + x kısayoluu kullanabilirsiniz.
C# veTry Catch Kullanımı
Belirli bir alandaki kodun derlenmesin sonucunda hata alınması durumunda yazılan programın çkmek yerine bir uyarı mesajı vermesi, ya da alternatif çözümlere yönelmesi için Try Catch ve bazen de Finally blokları kullanılır. Daha detaylı örnekleri ileride vermek üzere giriş yapacak kadar değinelim.
Aşağıdaki kodlar çalıştırılmak istendiğinde, dönüşüm gerçekleşemeyeceği için program hata verecektir ve çökecektir:
string musaitDegil = "doğru";
bool mesgul = Convert.ToBoolean(musaitDegil);
Eğer progamın çökmek yerine hata mesajı vermesini istersek:
try
{
string musaitDegil = "doğru";
bool mesgul = Convert.ToBoolean(musaitDegil);
}
catch (Exception)
{
Console.WriteLine("Beklenmeyen bir hata meydana geldi");
}
Bu program çalıştığında, kullanıcı ekranında sadece “Beklenmeyen bir hata meydana geldi” mesajını görmüş olacak ve çöken bir programın nahoşluğu ile karşılaşmamış olacaktır. Çalışma mantığı olarak ise, try bloğu iiçerisinde bir hata meydana gelirse catrch bloğu içerisindeki kodlar çalıştırılmaktadır.
C# Operatörleri
C# programlama dilinde 5 tür operatörden bahsedebiliriz:
1. Aritmetik operatörler
Toplam: a + b
Fark: a-b
Çarpım: a * b
Bölüm: a / b
Kalan (mod): a % b
Matematiksel işlemlerde, çarpım ve bölüm işlemlerinin önceliği vardır, fark ve toplam işlemleri sıa olarak daha sonra yapılır. Bu işlem öncelikleri sıralandırmaları parantezler ile güncellenebilir.
Diğer aritmetik operatörler:
Artış operatörü: a++ sayının değerini bir satır sonrasında 1 artırır, ++a sayının değerini bulunduğu noktada 1 artırır. a = a+1 olarak düşünülebilir
Azaltma operatörü: a-- sayının değerini bir satır sonrasında 1 azaltır, --a sayının değerini bulunduğu noktada 1 azaltır. a = a – 1 olarak düşünülebilir.
2. Karşılaştırma operatörleri
Eşittir: ==
Eşit değildir: !=
Büyüktür: >
Büyük eşittir: >=
Küçüktür: <
Küçük eşittir: <=
3. Atama operatörleri
Atama: =
Ekleme ataması: +=
Çıkartma ataması: -=
Çarpım ataması: *=
Bölüm ataması: /=
4. Mantıksal operatörler
Ve: &&
Veya: ||
Değil: !
5. Bit düzeyindeki operatörler (Bitwise)
Düşük seviye programlamada (sockets, şifreleme vb.) kullanıldığı için bitwise operatörler pek sık kullanılmamaktadırlar.
Ve: &
Veya: |
İlkel Olmayan Veri Türleri
İlkel veri türlerini gördükten sonra gelelim ilkel olmayan (non-primitive) veri türlerine. Sınıflar (class), yapılar (structure), vektörler (array), string ve enum veri türleri ilkel olmayan veri türleridir.
Sınıf (Class) Nedir?
Her uygulamanın sınıflardan oluştuğundan bahsetmiştik. Her bir sınıf, uygulama içerisinde farklı görevler üstlenirler ve bir araya gelerek uygulamayı oluştururlar.
Uygulamalar, katmanlardan oluşurlar:
1. Sunum
2. İş mantığı (Domain Layer)
3. Veri erişimleri (Persistence)
Bu üç katmanın her birisinin altında, farklı işlevleri yerine getiren sınıflar bulunmaktadır.
Sınıflar kendi altında iki bölümden oluşurlar.
1. Veri (Alanlar)
2. Davranış (Metodlar)
Sınıf, birbirleri ile ilişkili değişkenleri (alan/field) ve fonksiyonlaru (metod) birleştiren taslaklar olarak düşünülebilirler.
Örneğin bir kişinin;
adı, yaşı, mesleği, boyu gibi bilgiler alan (field)
Uyumak ya da zıplamak gibi işlevleri de metodları olarak tanımlanabilirler.
Sınıfları taslak olarak düşünmemizin nedeni, her bir sınıftan üretilen her bir nesnenin (object) bu özellikeri taşıması ve sahip olmasındandır. Nesneleri de sınıfların birer örneği olarak düşünebiliriz. Bu kişi sınıfından nesneler türeterek konunun daha iyi anlaşılmasını sağlamaya çalışalım.
Öncelikle sınıfımızı tanımlayalım.
Not: public bir erişim belirleyicidir. İlerleyen konularda diğer erişim belirleyiciler tanıtılıncaya kadar public kullanacağız.
class Kisi
{
public string Ad;
public string Soyad;
public void Zıpla()
{
Console.WriteLine("Zıpladı");
}
}
Kisi sınıfından üretilecek bir kişi nesnesi aşağıdaki gibi olabilir:
Kisi kisi = new Kisi();
kisi.Ad = "Mülayim";
kisi.Soyad = "Sert";
kisi.Zipla();
Kisi sınıfı, kisi ise bu sınıftan üretilen bir nesneyi temsil etmektedir ve kisi’nin özellikleri her bir nesne için farklı olabilir. Bir sınıfran birden fazla nesne yaratılabileceği için Kisi sınıfı sadece bir taslaktır.
Burada parametre almadan çalışan bir Zipla metodu yerine parametre alarak SelamVer gibi bir metod da yazılabilir.
Örnek olarak bir bilgisayar sınıfı yazalım:
public class Bilgisayar
{
public string Marka;
public string Model;
public int ModelYili;
public float Agirlik;
public void Calistir()
{
Console.WriteLine("Merhaba");
}
}
Nesne Nedir?
Sınıflar, temel olarak birer taslaktır. Bu taslak ile elde edilen ve bellekte yer kaplayan her bir örneğe ise nesne adı verilmektedir.
Sınıf alanında yazdığımız Bilgisayar sınıfından bir bilgisayar nesnesi oluşturalım ve bir metodunu kullanalım:
Bilgisayar bilgisayar = new Bilgisayar()
{
Marka = "Commodore", Model="64", ModelYili=1985, Agirlik=3.2f
};
bilgisayar.Calistir();
bilgisayar, artık Bilgisayar sınıfından oluşturulmuş bir nesnedir ve bellekte yer tutmaktadır. Benzer şekilde appleMacintosh, ibmMmx gibi nesneler de türetebiliriz.
Statik Metod Ne Anlama Gelmektedir?
Önceki örnekte tanımladığımız Kisi sınıfındaki bir metodu kullanabilmek için bir Kisi nesnesi (kisi) üretmek zorunda kaldık. Peki örneğin matematiksel işlemleri yaptırmak istediğimiz bir sınıf ve metodları olsa her işlem için nesne üretmek zorunda mı kalacağız? Nesne üretmeden direk olarak kullanabileceğimiz metodlar için static anahtar sözcüğü kullanılmaktadır.
public class HesapMakinesi
{
public static int Topla (int a, int b)
{
return a + b;
}
}
Artık HesapMakinesi sınıfının iki sayının toplamını almaya yarayan Topla metoduna erişmek için nesne üretmeye ihtiyacımız olmayacak:
int toplam = HesapMakinesi.Topla(3,5); //sonuç:8
Benzer şekilde nesnelerin de statik metodlarına erişmek mümkündür. Bu sayede birden fazla HesapMakinesi nesnesi (hesapmakinesi1, hesapmakinesi2 ...) oluşturulsa bile tüm nesnelerin bellek yönetiminde tek bir Topla yöntemi olacaktır ve üç farklı nesnenin üç farklı metodu gibi davranmak yerine üç farklı nesnenin ortak kulandığı tek bir metod olmuş olacaktır.
Dolayısıyla static anahtar sözcüğü de aslında bellekte sadece tek bir numunenin saklanması anlamına gelmektedir. Programın başlangıç noktası olan Main metodunun da static olmasınu buna örnek olarak düşünebilirsiniz, bellekte Main metodunun birden fazla örneği olmamalıdır.
Yapı (Struct) Nedir?
Yapı tanımlamak, sınıf tanımlamaya oldukça benzemektedir. Tanımlama aşamasındaki tek fark, class yerine struct anahtar sözcüğünün kullanılmasıdır. Yapılarda da sınıflarda olduğu gibi ikişkili alanlar ve metodlar birlikte yer almaktadır.
public struct Calisan
{
public int CalisanId;
public string Ad;
public DateTime IseBaslamaTarihi;
}
Peki class ile struct arasındaki farklar nelerdir?
Vektörler (Arrays) [Giriş Seviye]
Vektör, aynı veri tipindeki bir grup değişkeni saklamaya yarayan veri yapısı olarak tanımlanabilir.
Örneğin bir futbol takımının 11 oyuncusunun adı için 11 farklı string değişken tanımlamaktansa string veri tipinde 11 değer saklayabilen bir vektör kullanabiliriz.
string oyuncu1;
string oyuncu2;
//...
string oyuncu11;
yerine;
string[] oyuncular = new string[11];
artık köşeli parantezlerle yaratılan oyuncular değişkeni, içerisinde 11 string veri tipinde değer saklayabilen bir vektördür. Soldaki köşeli parantezler, oyuncular isminde bir string vektörü yaratmayı sağlarken, sağdaki [11] ifadesi ise vektörün kaç eleman saklayacağını tanımladığımız alandır. Yeni vektör oluştururken, sistemin bellekte ayrılması gereken yeri hesaplayabilmesi için vektörün boyutunu bilmesi gereklidir, bu sebeple vektör oluştururken boyutu da yazılmalıdır.
Vektör elemanlarına değerleri aşağıdaki gibi atanabilir.
Önemli Not: Vektör indisleri sıfırdan başlamaktadır, bu sebeple vektörün ilk elemanı 0 indisli elemanı, son elemanı ise eleman sayısı - 1 indisli elemandır.
string[] oyuncular = new string[11];
oyuncular[0] = "Turgay Şeren";
oyuncular[1] = "Can Bartu";
oyuncular[2] = "Lefter Küçükandonyadis";
//...
oyuncular[10] = "Metin Oktay";
Vektörleri yarattığımız esnada içerisine değerlerini de eklemek istersek kolay ve hızlı yazış biçimi (syntax):
string[] oyuncular = new string[11] { "Turgay Şeren", "Can Bartu", "Lefter Küçükandonyadis","...","...","...","...","...","...","...", "Metin Oktay" };
Ataması bu şekilde yapılan bir vektör elamanını çağırmak istersek de, aynı indis mantığı ile çağırabiliriz. Örneğin ilk eleman olan 0 indisli vektör elemanını çağırıp ekrana yazdırmak için:
Console.WriteLine(oyuncular[0]); //çıktı: Turgay Şeren
Son elemanı yazdırmak için ise, ilk indisimiz sıfırdan başladığı için 11 değil 10 yazmalıyız, 11 yaarsak hata alırız:
Console.WriteLine(oyuncular[11]); //hata
Console.WriteLine(oyuncular[10]); //çıktı: Metin Oktay
Eleman sayısını elle yazmak yerine, vektörün length özelliğini de kullanabiliriz. Oyuncular.length 11 elemanlı bir vektör için 11 gelmiş olacak, son eleman için de her koşulda 1 çıkartmamız gerekecektir.
Console.WriteLine(oyuncular[oyuncular.Length - 1]); //oyuncular[10] -> çıktı: Metin Oktay
String Nedir?
Çift tırnak içerisine yazılan her türlü yazıya kabaca String diyebiliriz.
string ad = "Turgay";
string adres = "xxx mahalesi yyy caddesi...";
String ifadeleri, aralarına + operatörünü koyarak birleştirebiliriz:
string isim = ad + " " +soyad
Ya da String Format ile biçimlendirdiğimiz şekliyle String’i şekillendirebiliriz:
string.Format("{0} {1}", ad,soyad );
string’in, bir vektör elemanlarını (aralarına istediğimiz metinleri de ekleyerek) string biçimine dönüştürmemizi sağlayan Join gibi yardımcı metodları vardır:
string oyuncularStr = string.Join(",",oyuncular);
Console.WriteLine(oyuncularStr); //çıktı: Turgay Şeren,Can Bartu,Lefter Küçükandonyadis,...,...,...,...,...,...,...,Metin Oktay
Kaçış (Escape) Karakterleri
Programlama dillerinde, satır atlamak ya da tab kadar boşluk bırakmak gibi özel kullanımları olan ifadeler vardır. Ayrıca özel anlamlar yüklenmiş olan ters slash gibi karakterlerin kendisini kullanmak istersek ne olacak? C# için de aşağıdaki kaçış karakterlerini kullanabiliriz:
\n: Yeni bir satıra geçer
\t: Tab kadar boşluk bırakır
\\: Ters slash işaretini ekleyebilmemizi sağlar
\’: Tek tırnak işareti ekleyebilmemizi sağlar
\": Çift tırnak işareti ekleyebilmemizi sağlar
Kaçış karakterlerine gerek kalmaksızın yazılan string’in içeriğini koruması için ise verbatim string adı verilen @ karakteri, çift tırnakların önüne konabilir:
string yazi = "Burada\nsatır atladım ve buraya da \'tırnak içerisinde\'\ttab kadar boşluk bıraktım";
çıktı:
Burada
satır atladım ve buraya da 'tırnak içerisinde' tab kadar boşluk bıraktım
verbatim string ile aynı örnek:
string yaziVerbatim = @"Burada
satır atladım ve buraya da 'tırnak içerisinde' tab kadar boşluk bıraktım";
çıktı:
Burada
satır atladım ve buraya da 'tırnak içerisinde' tab kadar boşluk bıraktım
String Biçimlendirme Metodları
String veri türündeki değişkenlerimizi biçimendirmek için kullanabileceğimiz yöntemlerdir:
ToLower(): Tümünü küçük harf yapar // ”merhaba reitix”
ToUpper(): Tümünü büyük harf yapar //”MERHABA REITIX”
Trim(): String’in etrafındaki white space (boşluk) karakterleri temizlemeye yarar
Not: Birden fazla metod yanyana kullanılabilir.
string merhaba = " Merhaba Reitix ";
Console.WriteLine("'"+merhaba.Trim()+ "'");//çıktı: 'Merhaba Reitix'
Console.WriteLine("'" + merhaba.Trim().ToUpper() + "'");//çıktı: 'MERHABA REİTİX'
String Arama Metodları:
Indexof(‘e’): e karakterinin sırasını (indeksini) bulmaya yarar
LastIndexof(“ti”): “ti” ifadesinin en son kaçıncı indekste göründüğünü bulur
String Substring (yazı içinde yazı) Metodları:
Substring(baslangic): Tek parametre alarak çalışan Substring metodu, bir string’i o sıradaki (indis) karakterden itibaren sonuna kadar alır
Substring(baslangic,uzunluk): İki parametre ile çalışan Substring metodu, ilk parametre olan sıradan itibaren 2. parametre sayısı kadar alır.
String Değiştirme Metodları:
Replace(“as”,”es”) ya da Replace(’a’,’e ’) metodları, soldaki içerikleri sağdakiler ile değiştirir.
string merhaba = "Merhaba";
Console.WriteLine(merhaba.Replace("aba","aba Reitix")); //Merhaba Reitix
String Null Kontrolü
String veri tipindeki bir değişkenin null olması ya da içerik olarak boş olmas kontrollerini sağlayabilmek için:
String.IsNullOrEmpty(merhaba);
String.IsNullOrWhiteSpace(merhaba);
String Bölme Parçalama Metodları:
Split metodu, string veri türündeki bir ifadeyi seçilen karakterleri dikkate alarak böler ve bir vektöre dönüştürür. Örnek olarak arasında boşluk olan her bir kelimeyi vektörün farklı bir elemanına dönüştüren Split metodu ile sadece 2. kelimeyi seçmek için vektörün 1 indisli elemanı alınır:
string merhaba = "Merhaba Reitix";
string merhabaSplit = merhaba.Split(' ')[1];
Console.WriteLine(merhabaSplit);//çıktı: Reitix
C# StringBuilder Kullanımı
StringBuilder, string veri türündeki içerikleri değiştirmenin ve birleştirmenin kolay ve hızlı bir yoludur. Aynı zamanda + operatörü ile yapılan string birleştirme işlemlerine göre çok daha hızlı ve verimlidir.
StringBuilder sınıfının kullandığı metodlar:
Append(): Bir string’in sonuna ekleme yapar
Insert(): Verilen indekste string’e ekleme yapar
Remove(): string’den bir başlangıç indeksinden itibaren istenilen sayıda karakteri siler
Replace(): string’den karakterleri değiştirmeyi sağlar
Clear(): string’i tamamen temizlemeyi sağlar
Yeni bir StringBuilder nesnesi yaratmak için:
StringBuilder yazi= new StringBuilder();
ya da
var yazi= new StringBuilder();
var yazi= new StringBuilder("merhaba");
kullanabiliriz.
var yazi= new StringBuilder();
yazi.Append("merhaba");
Console.WriteLine(yazi); //merhaba
//5 kere ! karakteri ekleyelim
yazi.Append('!', 5);
Console.WriteLine(yazi); //merhaba!!!!!
//! karakterini + yapalım
yazi.Replace('!', '+');
Console.WriteLine(yazi); //merhaba+++++
//+ karakterlerini silelim
yazi.Remove(7, 5);
Console.WriteLine(yazi); //merhaba
//En başa 3 kere ? koyalım
yazi.Insert(0, new string('?',3));
Console.WriteLine(yazi); //???merhaba
//metodları zincirleme ile arka arkaya da kullanabiliriz
yazi.Append(" rei").Append("tix");
Console.WriteLine(yazi); //???merhaba reitix
Enum Nedir?
Enum, isim değer çiftleri tanımlayabilmemizi sağlayan sabitlerdir.
Örneğin bir telefon bağlama sisteminde, hangi tuşa basıldığında hangi dilin bağlanacağını eğer değişkenlerle tanımlamak zorunda olsaydık:
const int tuslaIngilizce = 1;
const int tuslaAlmanca = 2;
const int tuslaFransızca = 3;
Bu mantığı daha kolay ve okunabilir bie biçimde enum olarak kodlayabiliriz:
public enum TuslamaDiller
{
Ingilizce = 1,
Almanca = 2,
Fransızca = 3
}
Not: Eğer sayı atamaları yapmamayı tercih ederseniz, ilk eleman 0 olarak atanacak ve birer artan sırayla numaralandırılmış olacaktır.
Not 2: enum’lar varsayılan olarak int veri türündedir. Eğer farklı bir veri türünde enum tanımlamak istenirse iki nokta ile enum adındna sonra belirtilmelidir.
Enum’ları kullanmak için de için de, adı üzerinden ID’sini getirmek istediğimi enum’larda:
Console.WriteLine(TuslamaDiller.Fransızca); // çıktı: Fransızca
Console.WriteLine((int)TuslamaDiller.Fransızca);// çıktı: 3
ya da ID’si ile değerini getirmek istediğimiz enum’lar için:
Console.WriteLine((TuslamaDiller)3); // çıktı: Fransızca
Enum sınıfının Parse metodu kullanılarak da aşağıdaki kullanım sağlanabilir:
var tus= (TuslamaDiller) Enum.Parse(typeof(TuslamaDiller), "Almanca");
C# ve Bellek Yönetimi
C# bellek yönetimi heap ve stack adı verilen iki farklı bölgede oluşur. Kullanılan veri tiplerine ve işlemlere göre (değişken kopyalamak gibi) sistemin hangi bellek türünü kullanacağı değişiklik gösterir. Temel olarak değer tiplerinin stack, referans tiplerinin ise heap bellek kullandıkları akılda tutulabilir.
Referans Tipleri ve Değer Tipleri
C#, yapı (structure) ve sınıf (class) biçimindeki ilkel ya da ilkel olmayan veri tiplerinden oluşmaktadır. İlk bölümde incelediğimiz ilkel veri tipleri (int, char, float, bool...) birer yapı, sonra incelediğimiz ilkel olmayan veri tipleri (string, vektörler, kendi tanımladığımız sınıflar... ) ise birer sınıftır. C#, bu tipleri bellek yönetimi açısından farklı değerlendirmektedir.
Değer Tipleri (Value Types): Yapılar (Structure)
- Stack’te depolanırlar
- Bellek atama işlemleri otomatik olarak gerçekleşir
- Scope dışına çıkıldığında bellekten otomatik olarak kaldırılırlar
Referans Tipleri (Reference Types): Sınıflar (Class)
- Heap’te depolanırlar
- Bellek ataması new anahtar sözcüğü ile manuel olarak yapılmalıdır
- Artıklar (garbage), runtime ya da CLR tarafından garbage collection süreci ile scope’tan çıkıldıktan bir süre sonra toplanır. Artık kullanılmayan değişkenlerin heap bellekten kaldırılmasından CLR sorumludur.
Değer tipi kopyalama süreci örneği:
var a = 15;
var b = a;
b++;
Console.WriteLine(string.Format("a= {0}, b={1}",a,b)); //a= 15, b=16
Değer (value) tipindeki değişkenleri kopyaladığınızda, kopyalanan değişkenin değeri alınır ve hedef değişkende (stack bellekte) depolanır. bu yüzden b değişkeninin değeri artırıldığında a’nın değeri sabit kalmıştır. Değer tiplerinde değerler kopyalanır.
Referans tipi kopyalama süreci örneği:
var vektor1 = new int[4] { 5, 10, 15, 20 };
var vektor2 = vektor1;
vektor2[0] = 100;
Console.WriteLine("vektor1 vektörü:" + string.Join(",",vektor1)); // vektor1 vektörü:100,10,15,20
Console.WriteLine("vektor2 vektörü:" + string.Join(",", vektor2)); // vektor2 vektörü:100,10,15,20
Bu veri tiplerine bu sebeple referans veri tipleri adı verilmektedir, çünkü kopyalardan birisi değiştirildiğinde heap içindeki aynı bellek adresleri güncelleneceği için her iki değişkenin de değeri güncellenmiş olacaktır.
Şartlı İfadeler
C# programlama dilinde şartlara bağlı olarak akışı yönlendirebileceğimiz üç farklı ifade vardır:
- If ve else ifadeleri
- Switch case ifadeleri
- Şarta bağlı ? operatörü: a ? b : c
C# if ve Else Kullanımı
Genel olarak if kullanımı taslağı şu şekildedir:
if (şart)
işlem yap
else if (başka bir şart)
başka bir işlem yap
else
kalan diğer durumlar için başka bir işlem yap
- Her bir şart, parantez içerisindeki ifadenin doğru (true) olması ile tetiklenir ve altındaki işlem ya da işlemler gerçekleştirilmiş olur.
- Bir if bloğunda en fazla tek bir koşul sağlanabilir, bir koşul sağlandığında devamındaki şartlar kontrol edilmeden bloktan çıkılır.
- if bloğunun sonunda else ifadesi varsa, bu kalan diğer tüm şartları kapsar ve kendinen önceki if ya da else if durumlarının tümünün çalışmaması halinde mutlaka çalışır, çünkü kalan tüm diğer durumları kapsar
- if şartlarının sonuna, C# yazım kurallarında alışkın olduğumuzun aksine ; (noktalı virgül) karakteri eklenmez
- Her bir if ifadesi, yeni bir if bloğunun başlangıcı anlamına gelir ve önceki bloktan tamamen bağımsızdır. Else if ve else ise kendinen önce bir if ifadesi olmasını gerektirir ve bağımsız olamazlar.
- Süslü parantezler {} ile scope’ları belirtilmedikçe her bir if/else if/else işlemi tek bir satır olarak dikkate alınır. Eğer bir satırdan fazla işlem yapılması isteniyorsa süslü parantezler içerisinde yazılmalıdırlar.
- if blokları içerisinde iç içe if blokları da kullanılabilir
if (şart)
{
if (başkaşart)
...
else
...
}
if (saat == 1)
Console.WriteLine("çok geç");
else if (saat>1 && saat<=5)
Console.WriteLine("çok erken");
else
Console.WriteLine("tam zamanında");
çıktı: çok erken
C# ve Switch/Case Kullanımı
if’te doğru ya da yanlış olmasına bağlı olarak şartlar ile mantıksal işlemler yapılır. Switch ifadelerinde ise bir değişkenin değerine göre durumlar (case) vardır.
int saat = 3;
switch (saat)
{
case 1:
Console.WriteLine("çok geç");
break;
case 2:
case 3:
case 4:
case 5:
Console.WriteLine("çok erken");
break;
default:
Console.WriteLine("tam zamanında");
break;
}
çıktı: çok erken
C# Soru işareti Operatörü ile Şarta Bağlı işlemler
şart ? doğru_ise_yapılacak_işlemler : yanlış_ ise_yapılacak_işlemler
? Operatörü Örnekleri:
bool sart = true;
int sayi;
sayi = sart ? 1 : 0;
Console.WriteLine(sayi); //sonuç: 1
C# ve Döngüler
C# programlama dilinde kullanabileceğimiz 4 farklı dmngü (iterasyon) seçeneği bulunmaktadır.
- For döngüleri
- Foreach döngüleri
- While döngüleri
- Do-While Döngüleri
C# For Döngüsü Nasıl Kullanılır?
for döngüsü, üç kısımdan oluşmaktadır:
for (int i = 1; i < 10; i++)
{
}
Birinci kısım: (int i=0) Döngünün başlangıç durumudur. Döngünün çalışmasını bu değişkenin değeri ile kontrol edebiliriz.
İkinci kısım: (i<10) Döngünün şart kısmıdır ve bu şart sağlandığı sürece döngü devam eder.
Üçüncü kısım: (i++) İterasyon ifadesidir, buradaki değişime göre artırma ya da azaltma opsiyonları ile iterasyonun gerçekleşmesi sağlanmış olur.
for (int i = 1; i < 10; i++)
{
Console.Write(i); //çıktı: 123456789
}
Foreach Döngüsü Nasıl Kullanılır?
Foreach döngüsü, enumerable (liste ya da vektör biçimineki) bir nesnenin elemanları ile bir döngü oluşturmak amacıyla kullanılır.
foreach (var sayi in sayilar)
{
//
}
C# While Döngüsü Nasıl Kullanılır?
while (şart)
{
//işlemler
}
Wile döngüleri, yazım olarak while’dan sonra yazılan parantez içerisinde yazılan koşul sağlandığı sürece çalışırlar. for döngülerinde olduğu gibi otomatik bir iterasyona sahip değillerdir, bu yüzden artırma ya da azaltma gibi işlemler while’ın içerisinde yapılmalıdır.
int i= 1;
while (i<10)
{
Console.Write(i);
i++;
}
çıktı: 123456789
C# Do While Döngüsü Nasıl Kullanılır?
do while döngüsünün while’dan tek farkı, önce işlemi yapıp ondan sonra kontrol etmesidir. Bu sebeple her do while döngüsü en az bir kere çalışır (while şartı false olsa bile). Bunun haricinde while döngüsü ile aynı işlemleri yapar.
int i= 1;
do
{
Console.Write(i);
i++;
}
while (i < 10);
çıktı: 123456789
int i= 100;
do
{
Console.Write(i);
i++;
}
while (i < 10);
çıktı: 100
Döngülerde Break ve Continue Kullanımı
Döngülerin içerisinde zaman zaman bir sonraki iterasyona atlamak ya da döngüyü tamamen durdurmak gerekebilmektedir. Bu sebeple break ve continue kullanılır:
break: Döngüden çıkar
continue: Bir sonraki iterasyona geçer
for (var j=1;j<10;j++)
{
if (j == 5)
break;
Console.Write(j);
}
çıktı: 1234
for (var j=1;j<10;j++)
{
if (j == 5)
continue;
Console.Write(j);
}
çıktı: 12346789
C# Döngü Örnekleri
Örnek 1: 100’e kadar olan çift sayıları azalan sırada alt alta yazdıralım:
for (int i=100 ; i>0 ; i--)
{
if (i%2 == 0)
Console.WriteLine(i);
}
Örnek 2: Turgay Şeren ismini for ve foreach döngüsü ile birer kere harflere ayırarak teker teker alt alta çıktı alalım:
string isim = "Turgay Şeren";
for (int i=0; i<isim.Length; i++)
{
Console.WriteLine(isim[i]);
}
foreach (char c in isim)
{
Console.WriteLine(c);
}
Örnek 3: 20’ye kadar olan tek sayıları while döngüsü kullanarak artan sırada yazdıralım:
int sayi = 0;
while (sayi<20)
{
if (sayi%2 == 1)
Console.WriteLine(sayi);
sayi++;
}
Örnek 4: for döngüsü ile sonsuz döngü oluşturalım
for (int i=1;i>0;i++)
{
Console.WriteLine("biri beni durdursun, bu "+i+". dönüşüm");
}
Örnek 5: while döngüsü ile sonsuz döngü oluşturalım:
int sayi = 1;
while (sayi>0)
{
Console.WriteLine("biri beni durdursun, bu " + sayi + ". dönüşüm");
sayi++;
}
Not: while(true) da daha kısa bir kullanımdır ve sonsuz döngü kurmanın en kısa yoludur.
Konsol Uygulamalarında Kullanıcıdan input (Girdi) almak
Programın çalışması esnasında kullanıcının değer girmesi için Console sınıfının ReadLine() yöntemi kullanılabilir.
Console.WriteLine("Lütfen adınızı yazınız");
string isim= Console.ReadLine();
Console.WriteLine("Merhaba "+isim);
/*
çıktı:
Lütfen adınızı yazınız
Turgay
Merhaba Turgay
*/
Vektörler (Arrays) [Orta Seviye]
Vektörleri aynı veri tipinde değerler saklayabilen değişkenler olarak tanımlamıştık. Şimdi de çok boyutlu vektörleri ve matrisleri inceleyelim.
İçerisinde 3 adet integer veri türünde veri saklayan tek boyutlu bir vektörün aşağıdaki gibi tanımlanabildiğini ve değerlerinin atanabildiğini görmüştük:
int[] sayilar = new int[3] { 5, 15, 25 };
3 satır, 4 sütundan oluşan bie matris tanımlamak istersek ise:
var sayilar = new int[3,4]
{
{ 5, 15, 25, 35 },
{ 45, 55, 65, 75 },
{ 85, 95, 105, 115 }
};
Benzer şekilde 2’den fazla boyutlu vektörler de yapılabilir. Örneğin 3 boyutlu bir vektör RGB tonları için kullanılabilir.
Aray sınıfının metodları ve özellikleri
Array.Length: Array’in eleman sayısını verir.
Array.Clear(): Aray’in belirli bir kısmını ya da tamamını temizlemeye (değerlerini silmeye) yarar.
Array.Copy(): Bir array’den diğer array’e istenilen sayıda elemanın kopyalanmasını sağlar
Array.IndexOf(): tek boyutlu bir array içinde bir değerin kaçıncı sırada olduğunu (birden çoksa ilki) bulur. 5 farklı overload’u vardır.
Array.Reverse(): Array elemanlarını tersten sıralar
Array.Sort(): Array elemanlarını sıralar
var anahtar sözcüğü ile daha kısa bir yazımla bir array tanımlayalım ve Array sınıfının özelliklerini bu array üzerinden inceleyelim:
var sayilar = new[] { 1, 3, 8, 11, 15 };
sayilar.Length; //5
Array.IndexOf(sayilar,8); //2
Array.Clear(sayilar,0,3); //ilk 3 eleman 0 olur --> 0 0 0 11 15
Array.Sort(sayilar); // zaten sıralı olduğu için 1 3 8 11 15
Array.Reverse(sayilar); // 15 11 8 3 1
Not: Bazı metodların (length gibi) nesne üzerinen erişilebilir olması, bu metodların statik olmamasından kaynaklanır. Statik metodlara Array sınıfından direk olarak erişilebilir.
C# Listeleri (List)
Array sınıfının sabit sayıda eleman saklayabildiğini gördük, peki değişken sayıda eleman ekleyip çıkartabileceğimiz kümeler için ne yapmalıyız? Bu sorunun yanıtlarından birisi, List kullanmaktır, çünkü List’ler değişken sayıda içerik saklayabilirler.
var sayilar = new List<int>();
Yeni bir List yaratırken kullandığımız <> karakterleri, List’in generic bir tip olmasından kaynaklanmaktadır.
Bir List’i değerlerini de tanımlayarak yaratmak istersek:
var sayilar = new List<int>() {5, 10, 15};
List generic sınıfının en sık kullanılan metodları:
Add(): List’e bir ekleme yapar
AddRange(): List’e birden fazla eleman (başka bir List ya da Array gibi) ekleyebilmemizi sağlar
Remove(): List’den bir eleman silmek için kullanılır
RemoveAt(): indeks numarası belirtilerek istenilen sıradaki elemanın silinmesi işlevini sağlar
IndexOf(): Bir elemanın kaçıncı sırada olduğunu (indeksi) bulmamızı sağlar
Contains(): List’in bir elemanı içerip içermediğini kontrol eder
Count(): List içerisindeki eleman sayısını verir
C#’ta Tarihlerle Çalışmak
Çeşitli veri türlerini inceledikten sonra tarih ve zaman ile ilgili veri tiplerine ve kullanımlarına bakabiliriz.
DateTime Veri Tipi
Datetime veri tipinde içinde bulunduğumuz zamanı da kullanarak ya da elle tanımlama yaparak nasıl değişkenler yaratabileceğimizi ve çıktı alabileceğimizi inceleyelim:
var simdi = DateTime.Now; //şimdi
var zaman = new DateTime(2019,8,12); // yıl ay gün
var bugun = DateTime.Today; //bugün
Console.WriteLine("Şu an saat:"+simdi.Hour+" dakika:"+simdi.Minute);//Şu an saat:20 dakika:37
var yarin = simdi.AddDays(1);//yarin
var dun = simdi.AddDays(-1);//dün
Console.WriteLine(simdi.ToLongDateString());// 12 Ağustos 2019 Pazartesi
Console.WriteLine(simdi.ToShortDateString());// 12.08.2019
Console.WriteLine(simdi.ToLongTimeString());// 20:37:33
Console.WriteLine(simdi.ToShortTimeString());// 20:37
Console.WriteLine(simdi.ToString());// 12.08.2019 20:37:33
Console.WriteLine(simdi.ToString("yyyy-MM-dd HH:mm"));// 2019-08-12 20:37
TimeSpan (Zaman Aralıkları) ile Çalışmak
var zamanAraligi = new TimeSpan(1, 15, 30); //1 saat 15 dakika 30 saniye
Console.WriteLine(zamanAraligi.Minutes); //15
Console.WriteLine(zamanAraligi.TotalMinutes); //75,5
Console.WriteLine(zamanAraligi.Add(TimeSpan.FromMinutes(10))); //01:25:30 -> 85,5 dakika
//////
var baslangic = DateTime.Now;
var bitis = DateTime.Now.AddHours(1);
var fark = bitis - baslangic; //fark bir TimeSpan'dir
Console.WriteLine(fark); //01:00:00.0000000
C#’ta Dosyalarla Çalışmak
C#’ta dosya işlemlerimiz için kullanacağımız namespace System.IO kütüphanesidir. File, FileInfo, Directory, DirectoryInfo, Path gibi tüm sınıflar System.IO namespace’i altındadır.
File ve FileInfo sınıfları: Dosyaları yaratmak, kopyalamak, silmek, taşımak ve açmak için File ve FileInfo sınıfları metodlarından yararlanılır. Aralarındaki temel fark, FileInfo’nun instance metodlara sahip olması; File sınıfının ise static sınıflara sahip olmasıdır. En sık kullanılan metodları ise:
Create(): Dosya yaratmak için kullanılır
Copy(): Dosyayı kopyalar
Delete(): Dosyayı siler
Exists(): Dosyanın olup olmadığını okontrol eder
GetAttributes(): Dosyanın özelliklerini getirir
Move(): Dosyayı taşır
ReadAllText(): Dosya içindeki tüm yazıları okur
Direcory ve DirectoryInfo Sınıfları: Klasör işlemleri için kullanılan sınıflardır. Benzer şekilde Directory sınıfı static metodlardan oluşurken DirectoryInfo sınıfı ise instance metodlardan oluşur. En sık kullanılan metodları:
CreateDirectory(): Klasör oluşturmaya yarar
GetCurrentDirectory(): Uygulamanın bulunduğu mevcut klasörün bilgisini verir
GetFiles(): Klasördeki dosyaları verir. Tüm dosyaların bilgileri alınabileceği gibi sadece belirli bir uzantıya sahip dosyaları alabilmek gibi arama ve filtreleme özellikleri de sunar.
GetLogicalDrives(): Harddisk’teki sürücülerin bilgilerini verir
Path sınıfı: Dosya ve klasörlerin erişim yollarına ilişkin metodlar sunan sınıftır. Path sınıfının bazı kullanışlı metodları:
GetDirectoryName(): Klasör adını verir
GetFileName(): Dosya adı bilgilerini verir
GetExtension(): Uzantı bilgilerini verir
GetTempPath(): Temporary folder (geçici) klasörün yolunu verir
Visual Studio Hata Ayıklama Araçları
Debug mode, Visual studio tarafından sunulan bir hata ayıklama metodudur ve breakpoint’ler konarak hangi aşamada hangi değişkenin değerinin ne olduğu takip edilerek uygulanır. Breakpoint koymak için F9 kısayolu kullanılır, tekrar F9 kısayoluna basılarak breakpoint kaldırılabilir. Uygulamayı debug (hata ayıklama) modunda çalıştırmak için ise F5 tuşuna basılır. Debug modu aktifken program her bir breakpoint’te durur ve kullanıcı denetlemelerini yaptıktan sonra F10 kısayol tuşu ile bir sonraki satıra geçer ve satır satır programın çalışması sonucu değişkenlerin değerlerini inceler. Her bir satır atlama işleminde mouse ile değişenlerin üzerine gelinerek güncel değerleri takip edilebilir. F11 kısayolu ile de Step Into işlemi; Shift+F11 kısayolu ile de Step Out işlemleri gerçekleştirilebilir. Debug yapılırken takip edilmek istenen değişkenler watch window’a eklenerek de benzer bir hata ayıklama işlemi gerçekleştirilebilir. Hata ayıklama işlemi tamamladığında debug modundan çıkmak için shift+F5 kısayolu kullanılır.
Tüm Breakpointleri tek seferde kaldırmak için Ctrl + Shift + F9 kısayolu kullanılabilir.
Nesne Odaklı Programlama (Object Oriented Programming) Nedir?
Nesne odaklı programlama, sadece sınıflar ve nesneleri kullanan uygulamalar anlamına gelmemektedir. Nesne odaklı programlamanın tam olarak anlaşılabilmesi için aşağıdaki programlama konseptlerinin anlaşılması ve uygulanması gereklidir.
- Kapsülleme (Encapsulation)
- Kalıtım (Inheritence)
- Çok biçimlilik (polymorphism)
Yapıcı (Constructor) Nedir?
Yapıcılar, sınıflardan nesneler oluştururken alanlardan bazılarının değerlerini tanımlama esnasında yapabilmemizi sağlar.
class Bilgisayar
{
public Bilgisayar()
{
}
}
- Yapıcılar sınıf adı ile aynı olmalıdır ve bu zorunludur.
- Yapıcıların metodlar gibi bir return tipi yoktur (void vb.)
- Default (varsayılan) yapıcı, yukarıdaki Bilgisayar() yapıcısı gibi parameter almadan yazılırlar.
- Her bir sınıf için yapıcı tanımlamak şart değildir, kullanıcı tarafından bir yapıcı tanımlaması yapılmazsa arka planda C# derleyicisi tarafından bir varsayılan yapıcı hazırlanır. Sınıf kodlarında bu yapıcı görünmez ama derleme sonucunda IL (Intermediate Language) kod tarafında oluşturulur.
- Yapıcıın amacı, alan değerlerine varsayılan değerlerini vermektir. Eğer yapıcı tarafından bir değer verilmezse her değişken kendi değişken tipinin varsayılan değerini alır. Örnek olarak int’in varsayılan değeri 0, bool bir ifadenin varsayılan değeri ise false olur. Referans tip (string gibi) değişkenlerin varsayılan değerleri ise null’dur.
class Bilgisayar
{
public string Marka;
public Bilgisayar(string marka)
{
this.Marka = marka;
}
}
Bilgisayar sınıfındaki Marka alanına değer vermek için kodlanmış tek parametreli bir Bilgisayar yapıcısı yukarıdaki gibidir. Bu yapıcı ile yeni bir bilgisayar nesnesi yaratmak için:
Bilgisayar bilgisayar = new Bilgisayar("Commodore");
Console.WriteLine(bilgisayar.Marka);// çıktı: Commodore
Not: Bir sınıf için birden fazla yapıcı tanımlanabilir.
Yapıcı Overloading (Yapıcı Aşırı Yüklemesi) Nedir?
Yapıcı overloading, bir metodu farklı imzalarla (signature) çağırabilmek anlamına gelmektedir.
class Bilgisayar
{
public string Marka;
public string Model;
public int ModelYili;
public Bilgisayar()
{
}
public Bilgisayar(string marka)
{
this.Marka = marka;
}
public Bilgisayar(string marka, int modelyili)
{
this.Marka = marka;
this.ModelYili = modelyili;
}
}
Bu örnekte Bilgisayar sınıfı için 3 farklı yapıcı tanımlanmıştır. Bu da Yapıcıların overload edildiği anlamına gelmektedir. Her bir yapıcı farklı bir imza taşımaktadır. İlk yapıcı parametre almazken ikinci yapıcı sadece tek bir string parametre almaktadır. Üçüncü yapıcı ise string ve int parametreler almaktadır. Her bir signature parametre sırası ile benzersiz (unique) olmak zorundadır.
Not: Yeni bir yapıcı tanımlamak için ctor yazıdıktan sonra tab tuşuna basabilirsiniz, bu yapıcı tanımlamak için hazırlanmış bir kısayoldur.
Sınıf tanımlaması içerisinde List gibi initiate edilmesi gereken bir alan varsa ve birden fazla yapıcı kullanılıyorsa, bu initiation işlemini her bir yapıcı içinde ayrı ayrı yapmak yerine bir yapıcı, temel yapıcı olarak belirlenerek hangi yapıcı seçilirse seçilsin önce temel yapıcının çalışması sağlanabilir.
class Bilgisayar
{
public string Marka;
public List<Model> Modeller;
public Bilgisayar()
{
Modeller = new List<Model>();
}
public Bilgisayar(string marka) :this()
{
this.Marka = marka;
}
}
Artık string parametresi ile tanımlanan bir bilgisayar nesnesi, önce parametre almayan ilk yapıcıyı çalıştırarak Modeller listesini initiate etmiş olur, ondan sonra ikinci yapıcı ile nesne oluşturulmuş olur. Birden fazla yapıcı da this içerisindeki parametreler ile birbirlerine bağlanabilirler.
C# ve Metodları Aşırı Yüklemek (Overloading)
Yapıcılarda olduğu gibi, sınıfların metodlarında da overload ile farklı imzalarda kullanabiliriz.
Örnek olarak Matematik sınıfının Topla yöntemi için 2 farklı overload hazırlayalım. Toplamını hesaplamak istediğimiz sayılar 2 tane olursa ilk metodu, 3 tane olursa da ikinci metodu kullanırız. Daha fazla sayı toplamak istersek problemini biraz sonra çözmek üzere önce metod parametrelerini aşırı yüklemeyi inceleyelim.
class HesapMakinesi
{
public void Topla(int x, int y)
{
//topla
}
public void Topla(int x, int y, int z)
{
//topla
}
}
Şimdi, toplamını almak istediğimiz sayıların adedini önceden bilmek zorunda olmadığımız bir metodu nasıl yazabiliriz? metod parametre sayısı değişken olacaksa vektörlerden faydalanmamız nasıl olur? Bu da bir çözümdür ancak C# parametre sayısı problemini aşmak için zaten bir çözüm sunuyor, params anahtar sözcüğü ile değişken sayıda metod parametresi kullanabiliriz.
public static int Topla(params int[] sayilar)
{
// return ...;
}
Topla metodunu artık istediğimiz sayıda parametre ile kullanabiliriz.
int toplam = HesapMakinesi.Topla(5,5,10);
C# ref ve out Niteleyicileri
Bir metoda gönderdiğimiz parametrenin kendisinin güncellenerek geri gelmesini istersek ref ve out niteleyicilerinden faydalanırız. Değeri olmayan değişkenler için out, değeri olan değişkenler için ref kullanılır genellemesi yapılabilir.
class HesapMakinesi
{
public static void KaresiniAl(ref int a)
{
a = a * a;
}
}
int sayi = 5;
HesapMakinesi.KaresiniAl(ref sayi);
Console.WriteLine(sayi);//çıktı: 25
C# Readonly Alanlar
readonly niteleyicisi, bir alanın değerinin sadece bir kere atanabileceğini ya da bu değişkenin sadece bir kere initiate edilebileceğini ifade eder. Amacı ise birden fazla metod içerisinde initiate edilen alanlar içerisinde önceden kullanılan değerlerin kaybedilmemesi içindir. örneğin Siparisler isimli bir listeye yapıcı içerisinde eklenen siparişlerin sınıfa ait başka bir metod içerisinde yeniden initiate edilmesi, listeye önceden eklenen tüm siparişlerin kayıp olması anlamına gelecektir. Bu tip durumlara karşı koruma almak amacıyla readonly niteleyicisi kullanılmaktadır.
Programlamada Kapsülleme (Encapsulation) Nedir?
Uygulamaların sınıflardan oluştuğunu incelemiştik. Bu sınıflar, uygulamanın belirli bir işlevini yerine getirmek amacıyla çeşitli alanlara ve metodlara sahip olurlar. Kullanıcılar da bu alan ve metodları kullanarak uygulamayı kullanmış olurlar. Ancak sınıflar içerisindeki tüm alan ve sınıflar kullanıcıların erişimlerine açık olmak zorunda değildir, bu erişim sınırlama işlemine kapsülleme adı verilmektedir. Tüm programlama dillerine ortak olarak kullanılan kapsülleme işlemi şu şekilde gerçekleşir:
1. Alanlar private erişim niteleyicileri ile tanımlanırlar
2. Getirme ve atama (get/set) işlemleri, public metodlar içerisinde gerçekleştirilir.
Böylece kullanıcının alanlara direk olarak erişimlerine izin verilmemiş olur. Bazı durumlarda metodlar içerisinde sadece get ya da sadece get işlemleri konarak ile bu erişimler daha da sınırlandırılabilir. Bir alana erişimi sağlayan sınıf üyelerine özellik (property) adı verilir.
Not: private alanlar terminolojik olarak alt çizgi ile başlarlar ve küçük harflerle yazılırlar.
class Bilgisayar
{
private string _marka;
public string Marka
{
get { return _marka; }
set { _marka = value; }
}
}
Uzun uzun yazmak yerine aşağıdaki gibi otomatik olarak uygulanmış get/set işlemleri de kullanılabilir, bu işlemde kullanılan özelliklere auto-implented propery adı verilmektedir.
class Bilgisayar
{
public string Marka {get;set;}
}
Not: Yeni bir özellik yazmak için prop yazılarak tab tuşuna basılabilir, bu yeni bir property yazabilmek için tanımlanmış bir kısayoldur.
C# Özellik indeksleyicileri (C# Property Indexer)
İndeksleyici, bir listenin (liste, koleksiyon gibi) elemanlarına indeksi ile erişebilmeyi sağlayan özel bir tür özelliktir (property).
Çerez yönetimi yapmak için kendi sınıfımızı yazdığımızı varsayalım. Çerezler, dosya adı ve içeriklerden oluşurlar. Bu amaçla da Anahtar/Değer (key/value) çiftlerini saklamak için kullanılan bir generic Dictionary sınıfı alanını sınıfımıza ekleyelim. Bu dictionary içerisindeki her bir anahtar sözcüğe karşılık gelen değeri set ve get etmek için iki farklı metod yazmak yerine indeksleyicilerden fayalanabiliriz:
public class Cerez
{
private readonly Dictionary<string, string> _dictionary=new Dictionary<string, string>();
public string this[string key]
{
get { return _dictionary[key]; }
set { _dictionary[key] = value; }
}
}
Cerez cerez = new Cerez();
cerez["tarayici"] = "Chrome";
Console.WriteLine(cerez["tarayici"]);// çıktı: Chrome
inheritance (kalıtım) Nedir?
Kalıtım, birbirleri ile ilişkili iki sınıftan birisinin, diğerinin kodlarını kalıtsal olarak miras alabilmesidir. Arabanın bir araç olması, kalıtıma örnek olarak verilebilir. Kalıtım kodların tekrar kullanılabilirliklerini artırır ve tekrar tekrar yazılmalarının önüne geçer. Aynı zamanda ileride göreceğimiz çok biçimlilik (polimprphism) için de örnektir.
Hem sedan binek araçların (araba diyelim), hem de SUV’lerin taşıt sınıfının birer alt sınıfı (aynı özelliklere sahipler) olduğunu düşünerek aşağıdaki gibi bir kalıtım örneği verebiliriz.
public class Tasit
{
public int KapiSayisi { get; set; }
}
public class Araba : Tasit
{
}
public class Suv: Tasit
{
}
Araba ve Suv sınıflarının KapiSayisi gibi bir özellikleri olmamasına rağmen her iki sınıf da Otomobil sınıfından türetildikleri için otomobil sınıfının özelliklerini de miraz almış olurlar.
Araba araba = new Araba();
araba.KapiSayisi = 4;
Suv suv = new Suv();
suv.KapiSayisi = 4;
Kalıtımda Yapıcı Kullanımı
Taşıttan Suv kalıtmakla ilgili örneği görmüştük. Peki Tasit sınıfına ait yapıcıyı Suv sınıfı içerisinde nasıl kullanacağız?
public class Tasit
{
private string _sasinumarasi;
public Tasit (string sasiNumarasi)
{
_sasinumarasi = sasiNumarasi;
}
}
public class Suv : Tasit
{
public Suv(string sasiNumarasi)
{
//_sasinumarasi erişilmez olduğu için hata alınır
}
}
Sadece tek bir string parametre imzası ile kurucusu olan Tasit sınıfından türetilmiş olan Suv sınıfı, Tasit sınıfı içerisindeki private alana erişemediğin için hata alır. Bu problemi aşmak için base anahtar sözcüğü kullanılır.
public class Tasit
{
private string _sasinumarasi;
public Tasit (string sasiNumarasi)
{
_sasinumarasi = sasiNumarasi;
}
}
public class Suv : Tasit
{
public Suv(string sasiNumarasi) :base(sasiNumarasi)
{
//sasiNumarasi kurucu parametresi kalıtsal bir üst sınıfa kurucu parametresi olarak gönderilir
}
}
Yukarı Çevrim (Upcasting) ve Aşağı Çevrim (Downcasting) Nedir?
- Türetilmiş bir sınıftan ana (base) sınıfa yapılan dönüştürülme işlemine yukarı çevrim (upcasting) adı verilir.
- Bir ana sınıftan türetilmiş sınıfa yapılan dönüştürme işlemine ise aşağı çevrim (downcasting) adı verilir.
Ucgen ucgen = new Ucgen();
Sekil sekil = ucgen; //upcasting
//Sekil sekil2 = new Sekil();
//Ucgen ucgen2 = sekil; //hata
Ucgen ucgen2 = (Ucgen)sekil; //downcasting
Yukarı çevrim ve aşağı çevrim işlemleri için as ve is anahar sözcükleri kullanılır.
C# as ve is Anahtar Sözcükleri
as anahtar sözcüğü ile yapılan dönüştürme işlemlerinde hata (exception) oluşmaz, bunun yerine nesne null olabilir.
Sekil sekil = new Sekil();
Ucgen ucgen = sekil as Ucgen;
is anahtar sözcüğü ile de bir nesnenin tipi kontrol edilebilir.
if (ucgen is Sekil)
{
//...
}
Boxing ve Unboxing Nedir?
Object sınıfı, .NET framework’ünün temel sınıfıdır. Bu nedenle tüm sınıflardan oluşturulmuş nesneler object olarak tanımlanabililirler. Sınıfların da referans tipinde olduklarını görmüştük.
Sekil sekil = new Sekil();
object ucgen = sekil;
Peki object olarak tanılmanmış bir nesne değer tipindeki bir nesneye eşitlenmek istenirse ne olur?
Değer tipindeki bir nesneyi object referansına dönüştürme işlemine boxing adı verilmektedir.
int sayi = 10;
object obj = sayi;
//ya da
object obj2 = 10;
Unboxing işlemi ise bunun tam tersidir.
object obj = 10;
int sayi = (int)obj;
Composition (Birleşim) Nedir?
Kalıtımsal algoritmalar ile hazırlanmış uygulamalarda sınıfların birbirleri ile sıkı bağlanmış (tightly-coupled) olmalarından dolayı alt sınıflardaki çeşitliliğin artması, kalıtımın sağlandığı sınıflarda güncellemeler yapılmasına ya da ara sınıflar eklenmesine ihtiyaç doğurmaktadır. Örneğin Tasit sınıfından türeyecek bir Motor sınıfı eklersek, önceki taşıt seçenekleri için standart olduğunu düşündüğümüz kapı sayısı özelliği artık geçersiz olacaktır.
Yazılan kodların yeniden kullanılabilir olmalarını mümkün kılan bir diğer yaklaşım olan Composition (Birleşim) ise daha esnek bir yapıya sahiptir. Birleşim mantığında sınıfların hiyerarşik olarak birbirlerine sıkı sıkıya bağlandıkları bir “bir-şudur” yapısı yerine “şu-özelliği-vardır” mantığı geçerlidir. Örneğin önceki örneğimizde Suv bir Taşıttır demek yerine Suv’nin şu özellikleri vardır diyebiliriz. Bunun için de Interface (Arayüz) kullanılır.
Gerçek dünyada büyük çaplı uygulamalarda, inheritence ya da composition tekniklerinden sadece birisi seçilmek zorunda olmak gibi bir durum yoktur, uygulamanın kısmi ihtiyaçları doğrulltusunda her iki yaklaşım birden iç içe kullanılabilmektedir.
Erişim Niteleyicileri (Access Modifiers)
C# programlama dilinde beş farklı erişim niteleyicisi vardır. Erişim niteleyicileri, bir sınıfa ya da üyelerine erişimi kontrol edebilmeye yarayan güvenlik mekanizmalarıdır.
- Public: Her yerden erişilebilir
- Private: Sadece kendi sınıfı içerisinden erişilebilir
- Protected: Sadece kendi sınıfından ve o sınıftan türetilmiş sınıflardan erişilebilir
- Internal: Sadece aynı assembly içerisinden erişilebilir
- Protected internal: Sadece aynı assembly içerisinden ya da türetilmiş sınıf içerisinden erişilebilir.
Metod Overriding (Çiğnemek/Geçersiz Kılmak) Nedir?
Bir üst sınıftan kalıtılarak hazırlanmış bir sınıfın, üst sınıf içerisindeki bir metodu geçersiz kılarak yeniden tanımlaması işlemine override etmek adı verilir. Bir metodu override edebilmek için, bu metodun virtual ya da abstract olması gerekir.
public class Sekil
{
public virtual void Ciz()
{
Console.WriteLine("Şekil çizdim");
}
}
public class Ucgen : Sekil
{
public override void Ciz()
{
Console.WriteLine("Üçgen çizdim");
//base.Draw();
}
}
Ucgen ucgen = new Ucgen();
ucgen.Ciz();// çıktı: Üçgen çizdim
C# Abstact Niteleyicisi
abstract niteleyicisi, bir metodun türetilmiş sınıflar tarafından eklenmesini zorunlu kılan belirteçlerdir, içerilerinde kod olmaz. Dolayısıyla kalıtımsal olarak üretilen her alt sınıf, kendi üst sınıfının tüm abstract metodlarını override etmek zorundadır.
Bir metod abstract olarak nitelendirildiğinde, metodun sınıfı da abstract olmalıdır.
Abstract sınıflar initiate edilemezler.
Abstract sınıflar tasarlamanın amacı, bir temel kodun üzerinden çalışılacak olması durumunda tüm çalışanların temel kod tasarımını korumalarını sağlamaktır.
public abstract class Sekil
{
public abstract void Ciz();
}
public class Ucgen : Sekil
{
public override void Ciz()
{
Console.WriteLine("Üçgen çizdim");
}
}
C# Sealed Niteleyicisi
Sealed niteleyicisi, sınıflarda ya da metodlarda kullanılabilir.
Metodlarda kullanıldığında, abstract niteleyicisinin tam tersidir ve metodların override edilmesini engellemek amacıyla kullanılırlar. Sadece ana sınıftan kalıtılmış override metodlarda kullanılabilirler.
public class Ucgen : Sekil
{
public sealed override void Ciz()
{
Console.WriteLine("bu metodu override edemezsiniz");
}
}
public class EskenarUcgen : Ucgen
{
public override void Ciz() //hata
{
}
}
Sınıflarda kullanıldığında ise, sealed olarak işaretlenmiş olan sınıflardan kalıtım yapılamaz.
public sealed class Sekil
{
// bu sınıftan bir sınıf türetilemez
}
C# Interface (Arayüz) Nedir?
Arayüzler, bir uygulama içerisindeki bileşenler arasındaki ilişkinin esnek kurallarla tanımlamabilmesi ve daha az bağımlı olabilmeleri amacıyla kullanılırlar. Bu sayede bileşenlerden bazıları üzerinde yapılacak değişiklikler, diğer bileşenler üzerinde minimum etkiye sahip olacak ve dolaylı bağlantılı olduğu alanlarda en az değişikliğe ihtiyaç duymuş olacaktır.
Daha basit bir tanımlama ile, arayüzler bir çeşit anlaşma olarak düşünülebilirler. Bu yaklaşıma göre, bir arayüze sahip olan sınıflar, o arayüzün metodlarına sahip olmak zorundadırlar.
- İşlevleri açısından çok farklı olsalar da tanımlama şekilleri açısından sınıflara benzerler. class yerine interface yazılır.
- Arayüzler, yazım düzeni olarak I harfi ile başlarlar. Interface oldukları bu şekilde kolaylıkla anlaşılabilir.
- Sınıfların aksine, arayüzlerin içerisinde bir uygulama olmaz. Aşağıdaki örnekten de görülebileceği gibi Hesapla metodu sadece bir ifade olarak yer alır, süslü parantezler içerisinde kodları yoktur.
- Arayüz içerisindeki elemanların erişim belirleyicileri de olmaz.
- Bir sınıf, birden fazla sınıftan kalıtılamaz. Ancak birden fazla interface’e sahip olabilir.
Örnek bir arayüz şu şekilde hazırlanabilir:
public interface IHesaplayici
{
int Hesapla();
}
C# Generics
public class GenericListe<T>
{
public void Ekle(T deger)
{
///
}
}
T kelimesi Type (tip) anlamına gelmektedir ve bu alana generic listenin veri tipini yazabiliriz. Generic liste tip bağımsız olduğu için içerisinde ister int, istersek de özel bir sınıf nesnelerini depolayabiliriz. Her veri tipi için farklı bir liste hazırlama ihtiyacı böylece ortadan kalkmış olur.
var sayilar = new GenericListe<int>();
sayilar.Ekle(5);
Kitap kitap = new Kitap();
var kitaplar = new GenericListe<Kitap>();
kitaplar.Ekle(kitap);
Boxing ve Unboxing işlemlerine gerek kalmadan farklı veri tiplerinde generics kullanılabilir.
.NET içerisinde generic list olarak hazırlanmış sınıf kütüphaneleri sayesinde haricen bir generic sınıf hazırlamaya genelde gerek kalmamaktadır. System.Collections.Generic altından bu sınıf seçenekleri görüntülenebilir. ICollection, IDictionary, IEnumerable, IList, SortedList... gibi.
Key ve value çiftlerini saklayan bir generic dictionary (sözlük) hazırlamak istersek:
public class GenericDictionary<TKey, TValue>
{
public void Ekle(TKey key, TValue value)
{
///
}
}
TKey ve TValue veri tipleri artık int de olabilir özel bir sınıf da.
var sozluk= new GenericDictionary<int, Kitap>();
sozluk.Ekle(11, new Kitap());
Generic tiplerini sınırlamak için ise where anahtar sözcüğü kullanılır. Örnek olarak iki sayıdan maksimum olanını bulan bir metodu hem int, hem de generic veri tipi ile hazırlayalım:
public class Araclar
{
public int Maksimum (int a, int b)
{
return a > b ? a : b;
}
public T Maksimum<T> (T a, T b) where T: IComparable
{
return a.CompareTo(b) > 0 ? a : b;
}
}
Where ifadesi sınıf tanımlaması içerisinde yapılırsa, metod tanımında tekrar yazılmasına gerek olmaz.
public class Araclar<T> where T: IComparable
{
public int Maksimum (int a, int b)
{
return a > b ? a : b;
}
public T Maksimum (T a, T b)
{
return a.CompareTo(b) > 0 ? a : b;
}
}
C# Delegate Nedir?
Delegate, bir ya da birden fazla metodu çağırabilen nesnelere delegate adı verilir. Delegate’ler, fonksiyon referansları olarak da düşünülebilirler.
public class FotografIsleyici
{
public delegate void FotografFiltreHandler(Fotograf fotograf);
public void Isle (string yol, FotografFiltreHandler filtreHandler)
{
var fotograf = new Fotograf();
filtreHandler(fotograf);
}
}
public class FotografFiltreleri
{
public void UygulaParlaklik(Fotograf fotograf)
{
Console.WriteLine("parlaklık uygulandı");
}
public void UygulaContrast(Fotograf fotograf)
{
Console.WriteLine("contrast uygulandı");
}
public void YenidenBoyutlandir(Fotograf fotograf)
{
Console.WriteLine("Yeniden Boyutlandirildi");
}
}
static void Main(string[] args)
{
var isleyici = new FotografIsleyici();
var filtreler = new FotografFiltreleri();
FotografIsleyici.FotografFiltreHandler filtreHandler = filtreler.UygulaParlaklik;
filtreHandler += filtreler.UygulaContrast;
isleyici.Isle("gorsel.jpg", filtreHandler);
}