C# ve .NET: Güçlü ve Çok Yönlü Uygulamalar İçin Bir Arada
Yazılım geliştirme dünyasında, sağlam, ölçeklenebilir ve performanslı uygulamalar oluşturmak için kullanılan birçok teknoloji ve platform bulunmaktadır. Bu ekosistem içinde, Microsoft tarafından geliştirilen C# (C Sharp olarak okunur) programlama dili ve .NET platformu, özellikle kurumsal düzeyde uygulamalar, web servisleri, masaüstü yazılımları, mobil uygulamalar ve oyun geliştirmede öne çıkan güçlü bir ikilidir. C#, modern, nesne yönelimli ve tip güvenli bir dil olarak geliştiricilere esnek ve verimli bir çalışma ortamı sunarken, .NET platformu bu dilde yazılan kodların çalıştırılması, yönetilmesi ve farklı uygulama türlerine uyarlanması için kapsamlı bir altyapı ve kütüphane seti sağlar.
C#, Java'nın sözdizimine benzerliği ve C++'ın gücünden ilham alarak tasarlanmıştır, ancak daha basit, daha güvenli ve daha üretken olmayı hedefler. Otomatik bellek yönetimi (Garbage Collection), tip güvenliği, zengin standart kütüphaneler ve modern dil özellikleri (LINQ, async/await, pattern matching vb.) sayesinde geliştiricilerin karmaşık görevleri daha kolay bir şekilde yerine getirmesine olanak tanır. .NET platformu ise, C# (ve F#, VB.NET gibi diğer .NET dillerinde) yazılan kodları Ortak Dil Çalışma Zamanı (Common Language Runtime - CLR) üzerinde çalıştırarak platform bağımsızlığı (belirli ölçüde), dil birlikte çalışabilirliği ve güvenlik gibi önemli avantajlar sunar.
Başlangıçta sadece Windows platformuna odaklanan .NET Framework'ten, açık kaynaklı, çapraz platform (Windows, macOS, Linux) desteği sunan .NET Core'a ve nihayetinde tüm .NET dünyasını tek bir çatı altında birleştiren modern .NET (NET 5, .NET 6, .NET 7 ve sonrası) sürümlerine evrilen bu platform, sürekli olarak gelişmekte ve modern yazılım geliştirme ihtiyaçlarına cevap vermektedir. ASP.NET Core ile yüksek performanslı web uygulamaları ve API'ler, Entity Framework Core ile kolaylaştırılmış veri erişimi, Xamarin (ve şimdi .NET MAUI) ile platformlar arası mobil uygulama geliştirme gibi yetenekler, C# ve .NET'i günümüzün en popüler ve aranan teknoloji yığınlarından biri haline getirmiştir. Bu rehber, C# dilinin temellerinden başlayarak .NET platformunun mimarisine, nesne yönelimli programlama prensiplerine, temel kütüphanelere, web geliştirmeye ve ileri seviye konulara kadar geniş bir perspektif sunarak bu güçlü ikiliyi öğrenme yolculuğunuzda size kapsamlı bir kaynak olmayı hedeflemektedir.
C# Temelleri: Dilin Yapı Taşları
C# ile programlamaya başlamak için dilin temel sözdizimini, verileri nasıl temsil ettiğini (veri tipleri), bu verileri nasıl sakladığını (değişkenler), veriler üzerinde nasıl işlem yaptığını (operatörler) ve program akışını nasıl kontrol ettiğini (kontrol yapıları) anlamak gerekir.
Temel Sözdizimi, "Merhaba Dünya" ve Yorumlar
C#, C ve C++ gibi dillerden türediği için süslü parantezler { }
ile blokları, noktalı virgül ;
ile ifadeleri sonlandırmayı temel alır. Büyük/küçük harfe duyarlıdır.
Klasik bir "Merhaba Dünya" örneği:
using System; // System isim alanını kullanacağımızı belirtiriz
// C# 9 ve sonrası için Top-Level Statements ile daha kısa yazım:
// Console.WriteLine("Merhaba Dünya!");
// Geleneksel Yöntem (C# 8 ve öncesi veya açık sınıf/metot tanımı istendiğinde):
namespace MerhabaApp // Kodumuzu organize etmek için bir isim alanı
{
class Program // Programımızın ana sınıfı
{
static void Main(string[] args) // Programın başlangıç noktası olan ana metot
{
Console.WriteLine("Merhaba Dünya!"); // Konsola yazı yazdırır
}
}
}
using System;
: .NET'in temel kütüphanelerinden biri olan System
isim alanındaki sınıfları (örneğin Console
) doğrudan kullanmamızı sağlar.
namespace
: İlgili sınıfları ve diğer türleri mantıksal olarak gruplandırmak için kullanılır, isim çakışmalarını önler.
class Program
: C#'ta tüm kodlar sınıflar içinde bulunur. Program
genellikle konsol uygulamaları için varsayılan sınıf adıdır.
static void Main(string[] args)
: Bir uygulamanın giriş noktasıdır. Program çalıştırıldığında ilk bu metot çağrılır.
static
: Metodun, sınıfın bir örneği (nesnesi) oluşturulmadan çağrılabileceğini belirtir.
void
: Metodun bir değer döndürmediğini belirtir.
Main
: Metodun özel adıdır, giriş noktası olduğunu belirtir.
string[] args
: Programa komut satırından argümanlar geçilmesini sağlar.
Console.WriteLine(...)
: Konsol penceresine belirtilen metni yazdırır ve bir alt satıra geçer.
Yorumlar:
Veri Tipleri: Değer ve Referans Tipleri
C# statik tipli bir dildir, yani değişkenlerin tipleri derleme zamanında bilinmelidir. Veri tipleri temel olarak iki kategoriye ayrılır:
- Değer Tipleri (Value Types): Değişkenin doğrudan veriyi tuttuğu tiplerdir. Belleğin "stack" bölgesinde saklanırlar (genellikle). Bir değer tipi değişkeni başka bir değişkene atandığında, verinin bir kopyası oluşturulur.
- Basit Tipler (Simple Types):
- Tamsayılar:
sbyte
, byte
, short
, ushort
, int
(varsayılan), uint
, long
, ulong
- Ondalıklı Sayılar:
float
(F veya f son eki), double
(varsayılan), decimal
(M veya m son eki - finansal hesaplamalar için yüksek hassasiyet)
- Karakter:
char
(tek tırnak içinde tek karakter, örn: 'A'
)
- Mantıksal:
bool
(true
veya false
)
struct
Tipleri: Kullanıcı tanımlı değer tipleridir.
enum
Tipleri (Enumerations): Sabit değerler kümesini temsil eder.
- Nullable Değer Tipleri (
?
): Normalde null
olamayan değer tiplerinin (örn: int
, bool
) null
değer alabilmesini sağlar (örn: int?
, bool?
).
- Referans Tipleri (Reference Types): Değişkenin, verinin bellekteki adresini (referansını) tuttuğu tiplerdir. Verinin kendisi belleğin "heap" bölgesinde saklanır. Bir referans tipi değişkeni başka bir değişkene atandığında, sadece referans kopyalanır, her iki değişken de aynı veriyi işaret eder. Referans tipleri varsayılan olarak
null
olabilir.
class
Tipleri: Kullanıcı tanımlı referans tipleridir (OOP'nin temeli).
interface
Tipleri: Bir sözleşmeyi tanımlar.
delegate
Tipleri: Metotlara referans tutar.
- Diziler (Arrays): Aynı türden eleman koleksiyonları (örn:
int[]
, string[]
).
string
Tipi: Metinleri temsil eder. Özel bir durumdur; referans tipi olmasına rağmen karakter dizisi (immutable - değiştirilemez) gibi davranır.
object
Tipi: Tüm diğer tiplerin türediği temel tiptir. Herhangi bir türden değeri tutabilir (boxing/unboxing gerektirebilir).
// Değer Tipleri
int sayi1 = 10;
int sayi2 = sayi1; // sayi1'in değeri (10) kopyalanır
sayi2 = 20;
Console.WriteLine($"Sayı 1: {sayi1}"); // 10 (değişmedi)
Console.WriteLine($"Sayı 2: {sayi2}"); // 20
bool aktif = true;
decimal fiyat = 99.95m;
char harf = 'C';
int? opsiyonelSayi = null; // Nullable değer tipi
// Referans Tipleri
string isim1 = "Ahmet";
string isim2 = isim1; // Referans kopyalanır (ama string immutable olduğu için farklı davranır)
isim2 = "Mehmet";
Console.WriteLine($"İsim 1: {isim1}"); // Ahmet (string immutable)
Console.WriteLine($"İsim 2: {isim2}"); // Mehmet
int[] dizi1 = { 1, 2, 3 };
int[] dizi2 = dizi1; // Referans kopyalanır, aynı dizi işaret edilir
dizi2[0] = 99;
Console.WriteLine($"Dizi 1[0]: {dizi1[0]}"); // 99 (değişti!)
Console.WriteLine($"Dizi 2[0]: {dizi2[0]}"); // 99
object obj = sayi1; // Boxing: Değer tipini object'e çevirme
int sayi3 = (int)obj; // Unboxing: Object'i tekrar değer tipine çevirme
Değişken Tanımlama ve Atama
Değişkenler, veri saklamak için kullanılır. Tanımlanırken tipi belirtilir.
// Tip belirterek tanımlama ve atama
int yas = 30;
string adSoyad = "Ayşe Kaya";
double ortalama = 75.5;
bool gectiMi = true;
// Sadece tanımlama (varsayılan değer atanır: sayılar için 0, bool için false, referans tipleri için null)
int puan; // puan = 0
string sehir; // sehir = null
bool devam; // devam = false
// Sonradan değer atama
puan = 100;
sehir = "İstanbul";
devam = true;
// Sabit tanımlama (değeri değiştirilemez)
const double PI = 3.14159;
// PI = 3.14; // Hata! const değeri değiştirilemez
// var anahtar kelimesi (Tip çıkarımı - Implicit Typing)
// Derleyici, atanan değere göre değişkenin tipini otomatik belirler.
// Tanımlandığı anda değer atanmalıdır.
var mesaj = "Bu bir string"; // Derleyici mesaj'ın tipini string olarak belirler
var sayac = 0; // Derleyici sayac'ın tipini int olarak belirler
// var durum; // Hata! Başlangıç değeri atanmalı
// mesaj = 10; // Hata! mesaj'ın tipi string olarak belirlendi, int atanamaz
var
kullanımı kodu kısaltabilir ancak tipin açıkça belirtilmesi okunabilirliği artırabilir. Genellikle anonim tipler veya karmaşık generic tiplerle kullanıldığında faydalıdır.
Operatörler
Değerler üzerinde işlem yapmak için kullanılırlar:
- Atama Operatörleri:
=
, +=
, -=
, *=
, /=
, %=
- Aritmetik Operatörler:
+
, -
, *
, /
, %
(modülüs), ++
(arttırma), --
(azaltma)
- Karşılaştırma Operatörleri: İki değeri karşılaştırır ve
bool
sonuç döndürür (==
(eşitlik), !=
(eşitsizlik), <
, >
, <=
, >=
).
- Mantıksal Operatörler: Boolean değerler üzerinde işlem yapar (
&&
(VE - AND), ||
(VEYA - OR), !
(DEĞİL - NOT)).
- Bitsel Operatörler: Sayıların bitleri üzerinde işlem yapar (
&
, |
, ^
(XOR), ~
(tümleyen), <<
(sola kaydırma), >>
(sağa kaydırma)).
- Koşul (Ternary) Operatörü:
koşul ? deger_dogruysa : deger_yanlissa
- Null Birleştirme Operatörü (
??
): Soldaki operand null
değilse onu, null
ise sağdaki operandı döndürür.
string kullaniciAdi = gelenAd ?? "Misafir"; // gelenAd null ise "Misafir" atanır
- Null Koşul Operatörü (
?.
ve ?[]
): Bir nesne veya dizi referansı null
ise hata fırlatmak yerine doğrudan null
döndürerek null referans hatalarını önlemeye yardımcı olur.
int? karakterSayisi = musterininAdi?.Length; // musteri veya musterininAdi null ise karakterSayisi null olur, değilse Length'i alır.
string ilkRenk = renkler?[0]; // renkler null ise ilkRenk null olur.
is
Operatörü: Bir nesnenin belirli bir tipte olup olmadığını kontrol eder.
as
Operatörü: Bir nesneyi belirli bir tipe dönüştürmeye çalışır, başarısız olursa hata fırlatmak yerine null
döndürür.
typeof
Operatörü: Bir tipin System.Type
nesnesini döndürür.
sizeof
Operatörü: Belirli bir değer tipinin bellekte kapladığı boyutu (byte cinsinden) döndürür (unsafe
bağlam gerektirebilir).
int a = 10, b = 4;
int toplam = a + b; // 14
int fark = a - b; // 6
int carpim = a * b; // 40
double bolum = (double)a / b; // 2.5 (Tip dönüşümü önemli)
int kalan = a % b; // 2
bool esitMi = (a == 10); // true
bool buyukMu = (b > 5); // false
bool kosul1 = esitMi && !buyukMu; // true && !false => true && true => true
bool kosul2 = (a < 5) || (b == 4); // false || true => true
string durum = (toplam > 15) ? "Büyük" : "Küçük"; // "Küçük"
object obj = "Merhaba";
if (obj is string)
{
string metin = obj as string; // Güvenli dönüşüm
if (metin != null)
{
Console.WriteLine($"Metin uzunluğu: {metin.Length}");
}
}
Kontrol Yapıları
Programın akışını kontrol etmek için kullanılır.
if-else if-else
: Koşullara göre farklı kod bloklarını çalıştırır.
int not = 75;
if (not >= 85) {
Console.WriteLine("Pekiyi");
} else if (not >= 70) {
Console.WriteLine("İyi");
} else if (not >= 55) {
Console.WriteLine("Orta");
} else {
Console.WriteLine("Başarısız");
}
switch
: Bir ifadenin değerine göre farklı durumları (case) kontrol eder. Genellikle belirli sabit değerlere karşı kontrol yapılırken if-else if
'e göre daha okunabilir olabilir. break
ifadesi ile durumdan çıkılır.
char harfNotu = 'B';
switch (harfNotu) {
case 'A':
Console.WriteLine("Mükemmel");
break;
case 'B':
case 'C': // Birden fazla case aynı bloğu çalıştırabilir
Console.WriteLine("Başarılı");
break;
case 'D':
Console.WriteLine("Geçer");
break;
default:
Console.WriteLine("Başarısız");
break;
}
for
Döngüsü: Belirli sayıda tekrarlanan işlemler için kullanılır (başlangıç değeri, koşul, artış/azalış adımı).
for (int i = 0; i < 5; i++) { // 0'dan 4'e kadar (5 kez) döner
Console.WriteLine($"Döngü Adımı: {i}");
}
while
Döngüsü: Belirtilen koşul doğru olduğu sürece dönmeye devam eder. Koşul döngü başlamadan kontrol edilir.
int sayac = 0;
while (sayac < 3) {
Console.WriteLine($"While Sayaç: {sayac}");
sayac++;
}
do-while
Döngüsü: while
gibidir ancak koşul döngünün sonunda kontrol edilir, bu nedenle döngü gövdesi en az bir kez çalışır.
int cevap;
do {
Console.WriteLine("Bir sayı girin (çıkmak için 0):");
// cevap = Convert.ToInt32(Console.ReadLine()); // Gerçek uygulamada
cevap = (new Random()).Next(-1, 5); // Simülasyon
Console.WriteLine($"Girilen: {cevap}");
} while (cevap != 0);
foreach
Döngüsü: Bir koleksiyonun (dizi, List vb.) tüm elemanları üzerinde gezinmek için kullanılır. Daha basit ve hataya daha az açıktır.
string[] isimler = { "Ali", "Veli", "Ayşe" };
foreach (string isim in isimler) {
Console.WriteLine($"Merhaba {isim}");
}
break
: İçinde bulunduğu döngüden (for, while, do-while, foreach) veya switch bloğundan hemen çıkar.
continue
: Döngünün mevcut adımını atlar ve bir sonraki adıma geçer.
Metotlar (Fonksiyonlar)
Belirli bir işlevi yerine getiren, tekrar kullanılabilir kod bloklarıdır. Parametre alabilir ve bir değer döndürebilirler.
Tanımlama: [Erişim Belirleyici] [static] [Dönüş Tipi] MetotAdı([Parametre Listesi]) { // Metot Gövdesi }
- Erişim Belirleyici (Access Modifier): Metodun nereden erişilebileceğini belirler (
public
, private
, protected
, internal
). Varsayılan genellikle private
'tır.
static
(İsteğe bağlı): Metodun sınıfın örneği olmadan doğrudan sınıf üzerinden çağrılıp çağrılamayacağını belirler.
- Dönüş Tipi: Metodun geri döndüreceği değerin tipini belirtir. Değer döndürmüyorsa
void
kullanılır.
- MetotAdı: Metodun ismidir (PascalCase kullanılır).
- Parametre Listesi: Metodun dışarıdan alacağı değerleri tanımlar (
tip parametreAdı
şeklinde).
- Metot Gövdesi: Metodun çalıştıracağı kodları içerir.
return
ifadesi ile değer döndürülür (eğer dönüş tipi void
değilse).
using System;
public class Hesaplayici
{
// Değer döndürmeyen, parametresiz metot
public void MesajYazdir()
{
Console.WriteLine("Hesaplayıcı sınıfından mesaj.");
}
// Değer döndüren, parametreli metot
public int KareAl(int sayi)
{
return sayi * sayi;
}
// Birden fazla parametre alan metot
public double DikdortgenAlan(double uzunKenar, double kisaKenar)
{
if (uzunKenar <= 0 || kisaKenar <= 0)
{
// Hata yönetimi veya varsayılan değer döndürme
return 0;
}
return uzunKenar * kisaKenar;
}
// Static metot (Nesne oluşturmadan çağrılabilir)
public static void StaticMesaj()
{
Console.WriteLine("Bu static bir metottur.");
}
}
// Metotları kullanma
public class AnaProgram
{
public static void Calistir() // Main yerine örnek kullanım
{
Hesaplayici hesap = new Hesaplayici(); // Nesne oluşturma
hesap.MesajYazdir();
int kareSonuc = hesap.KareAl(7); // 49
Console.WriteLine($"7'nin karesi: {kareSonuc}");
double alan = hesap.DikdortgenAlan(5.0, 8.0); // 40.0
Console.WriteLine($"Dikdörtgen alanı: {alan}");
// Static metot çağırma
Hesaplayici.StaticMesaj();
}
}
.NET Platformu: C# Kodunun Çalışma Ortamı
.NET, Microsoft tarafından geliştirilen, çeşitli dillerle (başta C#, F#, VB.NET) uygulama geliştirmeyi sağlayan kapsamlı bir yazılım geliştirme platformudur. Kodun derlenmesi, çalıştırılması, bellek yönetimi ve temel kütüphanelere erişim gibi işlevleri yerine getirir.
.NET Evrimi: Framework vs. Core vs. Modern .NET
- .NET Framework (Eski): .NET'in ilk sürümüdür. Sadece Windows üzerinde çalışır. Windows masaüstü (WinForms, WPF), web (ASP.NET Web Forms, MVC) ve servis uygulamaları geliştirmek için kullanılır. Artık aktif geliştirme almamaktadır, sadece bakım modundadır.
- .NET Core (Açık Kaynak, Çapraz Platform): .NET Framework'ün modern, modüler, açık kaynaklı ve çapraz platform (Windows, macOS, Linux) alternatifi olarak geliştirilmiştir. Yüksek performanslı web uygulamaları (ASP.NET Core), mikroservisler, konsol uygulamaları ve bulut tabanlı çözümler için tasarlanmıştır.
- .NET 5, 6, 7, 8... (Modern .NET): .NET Core'un devamıdır ve .NET ekosistemini tek bir çatı altında birleştirmeyi hedefler. .NET Framework'ün adı artık kullanılmamaktadır. Her yıl yeni ana sürüm çıkar (Kasım ayında). LTS (Long-Term Support - Uzun Süreli Destek) sürümleri (örn: .NET 6, .NET 8) daha uzun süre desteklenir. Modern .NET, web, bulut, masaüstü (WPF, WinForms - Windows özelinde, MAUI - platformlar arası), mobil (.NET MAUI), oyun (Unity ile), IoT ve AI gibi çok geniş bir yelpazede uygulama geliştirmeyi destekler.
Yeni projeler için kesinlikle modern .NET sürümleri (.NET 6 veya sonrası) tercih edilmelidir.
Ana Bileşenler: CLR, CTS, CLS
- Ortak Dil Çalışma Zamanı (Common Language Runtime - CLR): .NET uygulamalarının çalıştığı temel yürütme motorudur. Şu görevleri yerine getirir:
- Bellek Yönetimi (Garbage Collection - GC): Artık kullanılmayan nesnelerin kapladığı belleği otomatik olarak temizler, bellek sızıntılarını önler.
- Kod Derleme (JIT - Just-In-Time): C# gibi dillerde yazılan kod önce Ortak Ara Dile (Common Intermediate Language - CIL veya IL) derlenir. CLR, uygulama çalıştırılırken bu CIL kodunu makine koduna (native code) anında (Just-In-Time) derler.
- Tip Güvenliği (Type Safety): Kodun tür kurallarına uymasını sağlar, geçersiz tür dönüşümlerini engeller.
- Güvenlik (Security): Kod erişim güvenliği (Code Access Security - CAS) gibi mekanizmalarla kodun yetkilerini yönetir.
- İstisna Yönetimi (Exception Handling): Hataların yönetilmesi için bir yapı sunar.
- Thread Yönetimi: Çoklu iş parçacıklarının yönetilmesine yardımcı olur.
- Ortak Tip Sistemi (Common Type System - CTS): .NET platformunda kullanılan tüm veri tiplerinin (değer/referans tipleri, sınıflar, arayüzler vb.) nasıl tanımlanacağını ve nasıl davranacağını belirleyen standart bir sistemdir. Farklı .NET dillerinin birbirleriyle uyumlu çalışmasını sağlar, çünkü hepsi aynı tip sistemini kullanır.
- Ortak Dil Şartnamesi (Common Language Specification - CLS): Farklı .NET dillerinin birbirleriyle sorunsuz bir şekilde etkileşim kurabilmesi için uyması gereken bir dizi kural ve özelliktir. Bir kütüphane CLS uyumlu (CLS-compliant) ise, CLS uyumlu herhangi bir .NET dili tarafından rahatlıkla kullanılabilir. Örneğin, C#'ta büyük/küçük harfe duyarlılık varken VB.NET'te yoktur; CLS, bir public üyenin sadece büyük/küçük harf farkıyla başka bir üyeden ayırt edilmemesi gerektiğini söyler.
Bu bileşenler, .NET'in dil bağımsızlığını, platformlar arası çalışabilirliğini ve geliştirici verimliliğini sağlayan temel yapı taşlarıdır.
Derleme Süreci ve CIL (MSIL)
.NET'te kodun çalıştırılması iki aşamalı bir derleme süreci içerir:
- Kaynak Kodundan CIL'e Derleme: C# (veya F#, VB.NET) kodunuz, dilin kendi derleyicisi (örn: Roslyn C# derleyicisi) tarafından Ortak Ara Dil'e (CIL - Common Intermediate Language, eski adıyla MSIL - Microsoft Intermediate Language) derlenir. CIL, platformdan bağımsız, düşük seviyeli bir dildir. Bu derleme sonucunda genellikle
.dll
(kütüphane) veya .exe
(uygulama) uzantılı Assembly (bütünleştirilmiş kod) dosyaları oluşur. Bu dosyalar CIL kodunu ve metadata'yı (tipler, üyeler hakkında bilgi) içerir.
- CIL'den Makine Koduna Derleme (JIT): Uygulama çalıştırıldığında, CLR'nin JIT (Just-In-Time) derleyicisi devreye girer. JIT derleyici, Assembly dosyasındaki CIL kodunu, uygulamanın çalıştığı belirli platformun (işlemci mimarisi, işletim sistemi) anlayabileceği makine koduna (native code) ihtiyaç duyulduğu anda (genellikle bir metot ilk kez çağrıldığında) derler. Derlenen makine kodu önbelleğe alınır, böylece aynı kod tekrar tekrar derlenmez.
Bu iki aşamalı süreç, .NET'in hem platform bağımsızlığı (CIL sayesinde) hem de yüksek performans (native koda derleme sayesinde) sunmasını sağlar. .NET Core ve sonrası ile birlikte AOT (Ahead-of-Time) derleme gibi alternatifler de sunulmaktadır, bu sayede uygulama dağıtılmadan önce doğrudan makine koduna derlenebilir, bu da başlangıç süresini iyileştirebilir.
Çöp Toplama (Garbage Collection - GC)
C# ve .NET, geliştiricinin manuel olarak bellek ayırması (allocate) ve serbest bırakması (deallocate) gerekliliğini ortadan kaldıran otomatik bir bellek yönetimi sistemi sunar. Bu sistemin kalbi Çöp Toplayıcı'dır (Garbage Collector - GC).
İşleyişi temel olarak şöyledir:
- Referans tipli nesneler oluşturulduğunda (
new
anahtar kelimesi ile), CLR bu nesneler için belleğin yönetilen yığın (managed heap) bölgesinden yer ayırır.
- GC, periyodik olarak veya bellek baskısı oluştuğunda çalışır.
- Çalıştığında, uygulamanın kök referanslarından (global değişkenler, statik alanlar, çalışan metotların yerel değişkenleri ve parametreleri) başlayarak erişilebilir (ulaşılabilir) olan tüm nesneleri işaretler.
- İşaretlenmeyen (yani artık hiçbir referans tarafından gösterilmeyen) nesnelerin "çöp" olduğuna karar verir.
- İşaretlenmeyen nesnelerin kapladığı bellek alanını geri kazanır (serbest bırakır).
- İsteğe bağlı olarak, kalan nesneleri bellekte sıkıştırarak (compaction) boş alanları birleştirebilir ve gelecekteki bellek ayırmalarını hızlandırabilir.
GC, geliştiricinin bellek yönetimi yükünü büyük ölçüde azaltsa da, nasıl çalıştığını anlamak performans optimizasyonu açısından önemlidir. Örneğin, gereksiz yere büyük nesneler oluşturmak veya nesnelere uzun süre referans tutmak GC üzerinde baskı oluşturabilir.
IDisposable
arayüzü ve using
ifadesi, dosya akışları, veritabanı bağlantıları gibi yönetilmeyen (unmanaged) kaynakları (bellek dışı kaynaklar) deterministik olarak serbest bırakmak için kullanılır ve GC'nin işini kolaylaştırır.
Nesne Yönelimli Programlama (OOP) İlkeleri
C#, tamamen nesne yönelimli bir dildir. OOP, yazılımı yeniden kullanılabilir, yönetilebilir ve anlaşılması kolay "nesneler" etrafında organize etmeyi amaçlayan bir programlama paradigmadır. Temel ilkeleri şunlardır:
Sınıflar (Classes) ve Nesneler (Objects)
- Sınıf (Class): Bir nesnenin planı veya şablonudur. Belirli bir türdeki nesnelerin sahip olacağı özellikleri (verileri - fields, properties) ve davranışları (metotları) tanımlar. Bir veri tipidir (referans tipi).
- Nesne (Object / Instance): Bir sınıfın bellekte oluşturulmuş somut bir örneğidir. Sınıf tanımında belirtilen özelliklere ve davranışlara sahiptir.
new
anahtar kelimesi ile oluşturulur.
// Sınıf Tanımı (Şablon)
public class Kedi
{
// Alanlar (Fields) - Genellikle private olur
private string renk;
private int yas;
// Özellikler (Properties) - Alanlara kontrollü erişim sağlar
public string Ad { get; set; } // Otomatik Uygulanan Özellik (Auto-Implemented Property)
public int Yas // Tam Özellik (Full Property)
{
get { return yas; } // Değeri okuma
set // Değeri atama (isteğe bağlı kontrol eklenebilir)
{
if (value >= 0)
{
yas = value;
}
else
{
Console.WriteLine("Yaş negatif olamaz!");
}
}
}
// Yapıcı Metot (Constructor) - Nesne oluşturulduğunda çalışır
public Kedi(string ad, int baslangicYas)
{
this.Ad = ad; // this, mevcut nesneyi referans eder
this.Yas = baslangicYas;
this.renk = "Bilinmiyor"; // Varsayılan renk
Console.WriteLine($"{Ad} isimli kedi oluşturuldu.");
}
// Metot (Davranış)
public void Miyavla()
{
Console.WriteLine($"{Ad} miyavlıyor!");
}
public void RenkAta(string yeniRenk)
{
this.renk = yeniRenk;
}
public string RenkGetir()
{
return this.renk;
}
}
// Nesne Oluşturma (Instance Alma)
Kedi kedi1 = new Kedi("Tekir", 2); // Yapıcı metot çağrılır
Kedi kedi2 = new Kedi("Pamuk", 1);
// Nesne Özelliklerine Erişme ve Değiştirme
kedi1.Yas = 3;
kedi2.RenkAta("Beyaz");
// Nesne Metotlarını Çağırma
kedi1.Miyavla(); // Tekir miyavlıyor!
kedi2.Miyavla(); // Pamuk miyavlıyor!
Console.WriteLine($"{kedi1.Ad}'in yaşı: {kedi1.Yas}"); // 3
Console.WriteLine($"{kedi2.Ad}'in rengi: {kedi2.RenkGetir()}"); // Beyaz
Kapsülleme (Encapsulation)
Bir nesnenin iç verilerini (alanlarını) gizleyerek ve bu verilere sadece nesnenin kendi metotları (veya özellikleri - properties) aracılığıyla kontrollü bir şekilde erişilmesini sağlama prensibidir. Bu, veri bütünlüğünü korur ve nesnenin iç yapısının dışarıdan doğrudan değiştirilmesini engeller.
C#'ta kapsülleme genellikle şu yollarla sağlanır:
- Alanları (fields)
private
yaparak dışarıdan erişimi engellemek.
- Bu özel alanlara erişmek ve onları değiştirmek için
public
özellikler (properties - get/set metotları) tanımlamak. Bu özellikler içinde doğrulama veya ek mantık uygulanabilir.
Yukarıdaki Kedi
sınıfı örneğinde yas
alanı private
yapılmış ve erişim Yas
özelliği (property) üzerinden kontrollü bir şekilde (negatif değer kontrolü ile) sağlanmıştır. renk
alanı da private
olup, RenkAta
ve RenkGetir
metotları ile yönetilmektedir.
Kalıtım (Inheritance)
Bir sınıfın (alt sınıf / türetilmiş sınıf / child class) başka bir sınıfın (üst sınıf / temel sınıf / parent class) özelliklerini ve metotlarını miras almasıdır. "bir ...dır" (is-a) ilişkisini modeller (örn: "Kopek bir Hayvan'dır"). Kod tekrarını azaltır ve hiyerarşik ilişkiler kurmayı sağlar.
C#'ta kalıtım, sınıf tanımında : UstSinifAdi
şeklinde belirtilir. Bir sınıf sadece tek bir sınıftan doğrudan kalıtım alabilir (çoklu kalıtım desteklenmez, ancak arayüzlerle benzer bir yapı kurulabilir).
Alt sınıf, üst sınıfın public
ve protected
üyelerine erişebilir. base
anahtar kelimesi ile üst sınıfın üyelerine (özellikle yapıcı metotlarına veya override edilen metotlarına) erişilebilir.
Yukarıdaki Kopek
sınıfı, Hayvan
sınıfından kalıtım alarak ad
özelliğini ve Hayvan
'ın yapıcı metodunu (super
yerine base
ile çağırarak) kullanır, ayrıca kendi cins
özelliğini ve getir
metodunu ekler, konus
metodunu ise override eder (geçersiz kılar).
public class Canli
{
public int Yas { get; set; }
public void NefesAl() { Console.WriteLine("Nefes alınıyor..."); }
}
public class Bitki : Canli // Canli sınıfından kalıtım
{
public void FotosentezYap() { Console.WriteLine("Fotosentez yapılıyor..."); }
}
public class Hayvan : Canli // Canli sınıfından kalıtım
{
public string Ad { get; set; }
public virtual void SesCikar() { Console.WriteLine("Bir ses çıkarıyor..."); } // virtual: override edilebilir
}
public class Kedi : Hayvan // Hayvan sınıfından kalıtım
{
public override void SesCikar() // override: Üst sınıf metodunu ezer
{
base.NefesAl(); // Üst sınıfın metodunu çağırabiliriz
Console.WriteLine($"{Ad} miyavlıyor...");
}
}
Çok Biçimlilik (Polymorphism)
"Çok biçimlilik" anlamına gelir. Aynı arayüzü (metot imzası) paylaşan nesnelerin, bu arayüze farklı şekillerde yanıt vermesi yeteneğidir. Kalıtım ve arayüzler aracılığıyla sağlanır.
- Override Etme (Method Overriding): Alt sınıfın, üst sınıftan miras aldığı bir metodu (
virtual
veya abstract
olarak işaretlenmiş) kendi uygulamasıyla yeniden tanımlamasıdır. Üst sınıf türünden bir referans alt sınıf nesnesini tuttuğunda, çağrılan metot nesnenin gerçek türüne göre belirlenir (geç bağlama - late binding).
- Overload Etme (Method Overloading): Aynı sınıf içinde, aynı isimde ancak farklı parametre listelerine (sayı, tür veya sıra olarak farklı) sahip birden fazla metot tanımlanmasıdır. Hangi metodun çağrılacağı derleme zamanında parametrelere göre belirlenir.
// Override örneği (Yukarıdaki Hayvan/Kedi örneğinde SesCikar metodu)
Hayvan hayvanRef = new Kedi { Ad = "Boncuk" }; // Üst sınıf referansı, alt sınıf nesnesini tutuyor
hayvanRef.SesCikar(); // Kedi sınıfındaki override edilmiş SesCikar metodu çalışır ("Boncuk miyavlıyor...")
// Overload örneği
public class HesapMakinesi
{
public int Topla(int a, int b)
{
Console.WriteLine("İki int toplandı.");
return a + b;
}
public double Topla(double a, double b)
{
Console.WriteLine("İki double toplandı.");
return a + b;
}
public int Topla(int a, int b, int c)
{
Console.WriteLine("Üç int toplandı.");
return a + b + c;
}
}
HesapMakinesi calc = new HesapMakinesi();
calc.Topla(5, 3); // İki int toplandı. çağrılır
calc.Topla(2.5, 3.7); // İki double toplandı. çağrılır
calc.Topla(1, 2, 3); // Üç int toplandı. çağrılır
Soyutlama (Abstraction)
Bir nesnenin karmaşık iç detaylarını gizleyerek sadece gerekli özelliklerini ve davranışlarını dışarıya sunma prensibidir. Kullanıcının nesneyi nasıl kullanacağına odaklanmasını sağlar, iç implementasyon detaylarıyla ilgilenmesini gerektirmez.
C#'ta soyutlama şu yollarla sağlanır:
- Arayüzler (Interfaces): Bir sınıfın uygulaması gereken metotları, özellikleri, olayları ve indexer'ları tanımlayan bir sözleşmedir. Hiçbir implementasyon içermez (C# 8.0 ile varsayılan implementasyon eklense de temel amaç sözleşmedir). Bir sınıf birden fazla arayüzü uygulayabilir.
interface IHareketEdebilir
{
void HareketEt(); // İmza (implementasyon yok)
int Hiz { get; set; } // Özellik imzası
}
public class Araba : IHareketEdebilir // Arayüzü uygulama
{
public int Hiz { get; set; }
public void HareketEt()
{
Console.WriteLine($"Araba {Hiz} km/s hızla hareket ediyor.");
}
}
- Soyut Sınıflar (Abstract Classes): Hem implemente edilmiş (gövdeli) hem de implemente edilmemiş (
abstract
- gövdesiz) üyeler içerebilen sınıflardır. Doğrudan nesnesi oluşturulamaz (new
ile), sadece kalıtım alınabilir. Alt sınıflar, üst sınıftaki tüm abstract
üyeleri override etmek zorundadır. Bir sınıf sadece tek bir abstract sınıftan kalıtım alabilir.
public abstract class Sekil // Soyut sınıf
{
public string Renk { get; set; }
public abstract double AlanHesapla(); // Soyut metot (gövdesi yok)
public void BilgiYazdir() // Somut metot
{
Console.WriteLine($"Bu bir {Renk} şekildir.");
}
}
public class Daire : Sekil
{
public double YariCap { get; set; }
public override double AlanHesapla() // Soyut metodu override etme zorunluluğu
{
return Math.PI * YariCap * YariCap;
}
}
Soyutlama, kodun daha esnek, genişletilebilir ve bakımı kolay olmasını sağlar.
Temel .NET Kütüphaneleri ve Kavramlar
.NET platformu, uygulama geliştirmeyi kolaylaştıran zengin bir Temel Sınıf Kütüphanesi (Base Class Library - BCL) sunar. Ayrıca C#'ın kendine özgü güçlü dil özellikleri bulunur.
Koleksiyonlar (Collections)
Verileri gruplamak ve yönetmek için çeşitli koleksiyon sınıfları sunulur (System.Collections.Generic
isim alanında).
List
: Dinamik boyutlu, tür güvenli (generic) bir liste. Eleman ekleme (Add
), silme (Remove
), index ile erişim gibi işlemler için kullanılır. Dizilere göre daha esnektir.
List isimler = new List();
isimler.Add("Ayşe");
isimler.Add("Fatma");
isimler.Add("Hayriye");
isimler.Remove("Fatma");
Console.WriteLine(isimler[0]); // Ayşe
foreach(string isim in isimler) { Console.WriteLine(isim); }
Dictionary
: Anahtar-değer çiftlerini saklayan bir koleksiyon (hash table tabanlı). Anahtarlar benzersiz olmalıdır. Belirli bir anahtara karşılık gelen değere hızlı erişim sağlar.
Dictionary yaslar = new Dictionary();
yaslar.Add("Ali", 30);
yaslar["Veli"] = 40; // Ekleme veya güncelleme
yaslar.Add("Ayşe", 25);
Console.WriteLine($"Veli'nin yaşı: {yaslar["Veli"]}"); // 40
if (yaslar.ContainsKey("Ali")) { /* ... */ }
foreach(KeyValuePair cift in yaslar)
{
Console.WriteLine($"{cift.Key}: {cift.Value}");
}
Queue
: FIFO (First-In, First-Out) mantığıyla çalışan kuyruk yapısı (Enqueue
ile eklenir, Dequeue
ile çıkarılır).
Stack
: LIFO (Last-In, First-Out) mantığıyla çalışan yığın yapısı (Push
ile eklenir, Pop
ile çıkarılır).
HashSet
: Benzersiz elemanları sırasız olarak saklayan bir küme. Eleman kontrolü (Contains
) çok hızlıdır.
Bunlar dışında SortedList
, LinkedList
gibi daha özel ihtiyaçlara yönelik koleksiyonlar da mevcuttur.
LINQ (Language Integrated Query)
LINQ, C# diline doğrudan entegre edilmiş güçlü bir sorgulama yeteneğidir. Farklı veri kaynakları (bellekteki koleksiyonlar, veritabanları, XML vb.) üzerinde SQL benzeri bir sözdizimi veya metot tabanlı (fluent) bir sözdizimi kullanarak filtreleme, sıralama, gruplama, projeksiyon gibi işlemler yapmayı sağlar.
- Sorgu Sözdizimi (Query Syntax): SQL'e benzer,
from
, where
, orderby
, select
, group by
gibi anahtar kelimelerle yazılır.
List sayilar = new List { 5, 12, 8, 3, 20, 7 };
// Sorgu Sözdizimi: 10'dan küçük sayıları bul ve sırala
var kucukSayilarSorgu = from sayi in sayilar
where sayi < 10
orderby sayi ascending // veya descending
select sayi;
Console.WriteLine("10'dan Küçük Sayılar (Sorgu):");
foreach (int s in kucukSayilarSorgu)
{
Console.WriteLine(s); // 3, 5, 7, 8
}
- Metot Sözdizimi (Method Syntax / Fluent Syntax): Standart sorgu operatörlerini (
Where
, OrderBy
, Select
, GroupBy
, First
, Count
, Sum
vb.) lambda ifadeleriyle birlikte metot çağrıları şeklinde kullanır. Genellikle daha esnek ve yaygındır.
List sehirler = new List { "İstanbul", "Ankara", "İzmir", "Bursa", "Antalya" };
// Metot Sözdizimi: 'a' harfi içeren şehirleri büyük harfle al
var aIcerekSehirler = sehirler
.Where(sehir => sehir.ToLower().Contains("a"))
.OrderBy(sehir => sehir.Length) // Uzunluğa göre sırala
.Select(sehir => sehir.ToUpper()); // Büyük harfe çevir
Console.WriteLine("\n'A' İçeren Şehirler (Metot):");
foreach(string s in aIcerekSehirler)
{
Console.WriteLine(s); // ANKARA, BURSA, ANTALYA, İSTANBUL
}
// İlk elemanı bulma veya varsayılan değeri alma
string ilkSehir = sehirler.FirstOrDefault(); // "İstanbul"
int ondanBuyukIlk = sayilar.FirstOrDefault(s => s > 10); // 12
// Toplam veya ortalama alma
int sayiAdedi = sayilar.Count(); // 6
double ortalama = sayilar.Average();
LINQ, kodun daha okunabilir, kısa ve verimli olmasını sağlar. Özellikle Entity Framework Core ile veritabanı sorguları yazarken çok güçlüdür.
Delegate'ler ve Event'ler
- Delegate (Temsilci): Bir veya daha fazla metoda referans tutabilen bir tiptir. Metot imzalarını (parametre tipleri ve dönüş tipi) tanımlar. Olay yönetimi (event handling), callback mekanizmaları ve LINQ gibi birçok .NET özelliğinin temelini oluşturur. Belirli bir imzaya uyan herhangi bir metot bir delegate değişkenine atanabilir veya parametre olarak geçilebilir.
// Bir delegate tanımı: int parametre alıp bool döndüren metotlara referans tutar
public delegate bool SayiKontrolDelegate(int sayi);
public class DelegateOrnek
{
public bool CiftMi(int n) { return n % 2 == 0; }
public bool PozitifMi(int n) { return n > 0; }
public void KontrolEt(int[] dizi, SayiKontrolDelegate kontrolMetodu)
{
Console.WriteLine($"\n'{kontrolMetodu.Method.Name}' metodu ile kontrol:");
foreach (int sayi in dizi)
{
if (kontrolMetodu(sayi)) // Delegate üzerinden metodu çağırma
{
Console.Write($"{sayi} ");
}
}
Console.WriteLine();
}
public void Calistir()
{
int[] sayilar = { 1, -2, 3, 4, -5, 6 };
// Delegate değişkeni oluşturma ve metot atama
SayiKontrolDelegate ciftKontrol = CiftMi;
SayiKontrolDelegate pozitifKontrol = PozitifMi;
KontrolEt(sayilar, ciftKontrol); // CiftMi metodu çalışır
KontrolEt(sayilar, pozitifKontrol); // PozitifMi metodu çalışır
// Anonim metot veya lambda ifadesi ile delegate kullanımı
KontrolEt(sayilar, delegate(int x) { return x < 0; }); // Negatifleri bul
KontrolEt(sayilar, x => x % 3 == 0); // 3'e bölünenleri bul (Lambda)
}
}
.NET, yaygın kullanılan imzalar için Action<...>
(değer döndürmeyen) ve Func<..., TResult>
(değer döndüren) gibi hazır generic delegate'ler sunar.
- Event (Olay): Bir sınıfta önemli bir durum değişikliği veya eylem gerçekleştiğinde (örn: butona tıklama, işlem tamamlama) diğer nesneleri bilgilendirmek için kullanılan bir mekanizmadır. Yayıncı-Abone (Publisher-Subscriber) desenini uygular. Event'ler genellikle delegate'ler üzerine kuruludur. Bir sınıf bir event tanımlar (
event
anahtar kelimesi), diğer sınıflar bu event'e abone olur (+=
operatörü ile olay işleyici metotlarını ekler) ve event tetiklendiğinde (yayıncı sınıf tarafından çağrıldığında) abone olan tüm metotlar çalıştırılır. Abonelikten çıkmak için -=
kullanılır.
public class Buton
{
// Olay tanımı (EventHandler delegate'i yaygın kullanılır)
public event EventHandler Tiklandi;
// Olayı tetikleyen metot (genellikle protected virtual olur)
protected virtual void OnTiklandi(EventArgs e)
{
// Abone olan var mı kontrol et
// EventHandler kopyası alınıp null kontrolü yapılır (thread-safe)
EventHandler handler = Tiklandi;
if (handler != null)
{
handler(this, e); // Abone olan metotları çağır
}
}
// Butona basılmasını simüle eden metot
public void Bas()
{
Console.WriteLine("Butona basıldı!");
OnTiklandi(EventArgs.Empty); // Olayı tetikle
}
}
public class Form
{
public void ButonTiklandiHandler(object sender, EventArgs e)
{
Console.WriteLine("Form: Buton tıklama olayı yakalandı!");
// Gerekli işlemleri yap...
}
public void Calistir()
{
Buton btn = new Buton();
// Event'e abone olma
btn.Tiklandi += ButonTiklandiHandler;
btn.Tiklandi += (sender, e) => Console.WriteLine("Lambda ile abone olundu!"); // Lambda ile abone olma
btn.Bas(); // Olayı tetikle
// Abonelikten çıkma
// btn.Tiklandi -= ButonTiklandiHandler;
}
}
İstisna Yönetimi (Exception Handling)
Program çalışırken beklenmedik durumlar veya hatalar (örn: dosyayı bulamama, sıfıra bölme, ağ bağlantı hatası) oluştuğunda programın çökmesini engellemek ve bu durumları yönetmek için istisna yönetimi kullanılır.
try-catch-finally
bloğu kullanılır:
try
Bloğu: Hata oluşma potansiyeli olan kodlar buraya yazılır.
catch (ExceptionType ex)
Bloğu: try
bloğunda belirli türde bir istisna (exception) fırlatılırsa bu blok çalışır. Farklı hata türleri için birden fazla catch
bloğu olabilir (daha spesifik olan önce yazılmalıdır). ex
değişkeni hata hakkında detaylı bilgi içerir. Genel Exception
türü tüm hataları yakalar.
finally
Bloğu (İsteğe bağlı): Hata oluşsa da oluşmasa da her zaman çalıştırılır (kaynakları serbest bırakmak için - dosya kapatma, bağlantı sonlandırma vb.).
throw
İfadesi: Özel bir istisna fırlatmak veya yakalanan bir istisnayı tekrar fırlatmak için kullanılır.
public void DosyaOku(string dosyaYolu)
{
StreamReader reader = null; // finally bloğunda erişmek için dışarıda tanımla
try
{
reader = new StreamReader(dosyaYolu);
string satir = reader.ReadLine();
while (satir != null)
{
Console.WriteLine(satir);
satir = reader.ReadLine();
}
Console.WriteLine("Dosya okuma tamamlandı.");
// Bilerek hata fırlatma örneği (test için)
// throw new InvalidOperationException("Test hatası!");
}
catch (FileNotFoundException ex) // Spesifik Hata Yakalama
{
Console.WriteLine($"Hata: Dosya bulunamadı! ({ex.FileName})");
}
catch (IOException ex) // Daha genel I/O hatası
{
Console.WriteLine($"Hata: Dosya okuma hatası! ({ex.Message})");
}
catch (Exception ex) // Diğer tüm hataları yakalama (en sona yazılmalı)
{
Console.WriteLine($"Beklenmedik bir hata oluştu: {ex.Message}");
// throw; // Yakalanan hatayı tekrar fırlatmak isterseniz
}
finally
{
// Hata olsa da olmasa da çalışır (kaynağı serbest bırak)
if (reader != null)
{
reader.Close(); // veya reader.Dispose();
Console.WriteLine("StreamReader kapatıldı.");
}
}
}
// using ifadesi (IDisposable kaynakları otomatik serbest bırakır)
public void DosyaOkuUsing(string dosyaYolu)
{
try
{
// using bloğu bittiğinde reader.Dispose() otomatik çağrılır
using (StreamReader reader = new StreamReader(dosyaYolu))
{
string icerik = reader.ReadToEnd();
Console.WriteLine("\nDosya İçeriği (using ile):\n" + icerik);
} // reader.Dispose() burada çağrılır
}
catch (Exception ex)
{
Console.WriteLine($"Hata (using): {ex.Message}");
}
}
Veritabanı İşlemleri: Entity Framework Core
Modern uygulamaların çoğu verileri kalıcı olarak saklamak için veritabanları kullanır. .NET, veritabanlarıyla etkileşim kurmak için çeşitli teknolojiler sunar. Bunlardan en popüler ve modern olanı Entity Framework Core'dur (EF Core).
EF Core, nesne ilişkisel eşleyici (Object-Relational Mapper - ORM) bir kütüphanedir. Geliştiricilerin .NET nesnelerini kullanarak (SQL sorguları yazmadan) veritabanı işlemleri (CRUD - Create, Read, Update, Delete) yapmasını sağlar. Veritabanı tablolarını C# sınıflarına (Entities), satırları nesnelere eşler.
Temel EF Core Kavramları: DbContext, DbSet, Entity
- Entity (Varlık): Veritabanındaki bir tabloyu temsil eden basit bir C# sınıfıdır (POCO - Plain Old CLR Object). Genellikle tablodaki sütunlara karşılık gelen özelliklere (properties) sahiptir.
- DbContext (Veritabanı Bağlamı): Veritabanı ile uygulama arasındaki ana etkileşim noktasıdır. Veritabanı bağlantısını yönetir, veritabanı şemasını (hangi tabloların olduğu) bilir ve veritabanı sorgularını oluşturup yürütür. Genellikle
DbContext
sınıfından türetilir.
- DbSet:
DbContext
içinde, belirli bir Entity türü (ve dolayısıyla veritabanı tablosu) için bir koleksiyonu temsil eder. LINQ sorguları bu DbSet
nesneleri üzerinden yapılır.
using Microsoft.EntityFrameworkCore; // Gerekli using ifadesi
using System.Collections.Generic;
// Entity Sınıfı
public class Urun
{
public int UrunId { get; set; } // Primary Key (varsayılan olarak Id veya UrunId isminden anlaşılır)
public string Ad { get; set; }
public decimal Fiyat { get; set; }
public int StokAdedi { get; set; }
}
// DbContext Sınıfı
public class UygulamaDbContext : DbContext
{
// Veritabanındaki 'Urunler' tablosunu temsil eden DbSet
public DbSet Urunler { get; set; }
// Veritabanı bağlantı ayarları (genellikle appsettings.json'dan alınır)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Örnek: SQL Server bağlantısı (ConnectionString normalde dışarıdan verilir)
if (!optionsBuilder.IsConfigured) // Eğer dışarıdan konfigüre edilmediyse
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EFCoreOrnekDb;Trusted_Connection=True;");
}
}
// İsteğe bağlı: Model yapılandırması (Fluent API)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.Property(u => u.Fiyat)
.HasColumnType("decimal(18,2)"); // Fiyat sütununun veritabanı tipini belirle
}
}
EF Core'u kullanmak için projenize ilgili NuGet paketlerinin (Microsoft.EntityFrameworkCore
, Microsoft.EntityFrameworkCore.SqlServer
veya ilgili veritabanı sağlayıcısı, Microsoft.EntityFrameworkCore.Tools
) eklenmesi gerekir.
CRUD İşlemleri ve LINQ Sorguları
DbContext
ve DbSet
kullanılarak veritabanı üzerinde temel CRUD işlemleri ve LINQ ile sorgulama yapılır.
using System.Linq; // LINQ için gerekli
public class VeritabaniIslemleri
{
public void Calistir()
{
// DbContext örneği oluştur (using ile otomatik Dispose sağlanır)
using (var context = new UygulamaDbContext())
{
Console.WriteLine("Veritabanı işlemleri başlıyor...");
// --- CREATE (Oluşturma) ---
Console.WriteLine("\nYeni ürün ekleniyor...");
var yeniUrun = new Urun { Ad = "Laptop X", Fiyat = 25000m, StokAdedi = 15 };
context.Urunler.Add(yeniUrun); // Değişikliği bellekte takip etmeye başla
context.SaveChanges(); // Değişiklikleri veritabanına kaydet
Console.WriteLine($"Eklenen ürün ID: {yeniUrun.UrunId}");
// --- READ (Okuma - LINQ ile Sorgulama) ---
Console.WriteLine("\nTüm ürünler okunuyor:");
var tumUrunler = context.Urunler.ToList(); // Tüm ürünleri çek
foreach (var urun in tumUrunler)
{
Console.WriteLine($"- {urun.Ad} ({urun.Fiyat:C}) - Stok: {urun.StokAdedi}");
}
Console.WriteLine("\nFiyatı 20000'den yüksek ürünler:");
var pahaliUrunler = context.Urunler
.Where(u => u.Fiyat > 20000m)
.OrderBy(u => u.Ad)
.ToList();
foreach (var urun in pahaliUrunler)
{
Console.WriteLine($"- {urun.Ad}");
}
Console.WriteLine("\nID'si 1 olan ürünü bulma:");
// FirstOrDefault: Bulamazsa null döner
var bulunanUrun = context.Urunler.FirstOrDefault(u => u.UrunId == yeniUrun.UrunId);
// Find: Primary Key ile arama (önce bellekte arar, yoksa DB'ye gider - daha hızlı olabilir)
// var bulunanUrun = context.Urunler.Find(yeniUrun.UrunId);
if (bulunanUrun != null)
{
Console.WriteLine($"Bulunan: {bulunanUrun.Ad}");
// --- UPDATE (Güncelleme) ---
Console.WriteLine("\nÜrün güncelleniyor...");
bulunanUrun.Fiyat *= 0.9m; // Fiyatı %10 düşür
bulunanUrun.StokAdedi -= 1;
context.SaveChanges(); // Değişiklikleri kaydet
Console.WriteLine($"Güncellenen fiyat: {bulunanUrun.Fiyat:C}, Stok: {bulunanUrun.StokAdedi}");
}
else
{
Console.WriteLine("Güncellenecek ürün bulunamadı.");
}
// --- DELETE (Silme) ---
if (bulunanUrun != null)
{
Console.WriteLine("\nÜrün siliniyor...");
context.Urunler.Remove(bulunanUrun); // Silme için işaretle
context.SaveChanges(); // Değişiklikleri kaydet
Console.WriteLine($"{bulunanUrun.Ad} silindi.");
}
// Silindiğini doğrulama
var silinmisUrun = context.Urunler.Find(yeniUrun.UrunId);
if(silinmisUrun == null) {
Console.WriteLine("Ürün başarıyla silinmiş.");
}
} // context.Dispose() burada otomatik çağrılır
}
}
SaveChanges()
metodu çağrılana kadar yapılan değişiklikler (Add, Update, Remove) sadece bellekte tutulur, veritabanına yansımaz. Bu metot, yapılan tüm değişiklikleri tek bir işlem (transaction) içinde veritabanına gönderir.
Migrations (Veritabanı Geçişleri)
Uygulama geliştirme sürecinde Entity sınıflarınız (ve dolayısıyla veritabanı şemanız) zamanla değişebilir (yeni tablo ekleme, sütun değiştirme vb.). EF Core Migrations, bu model değişikliklerini veritabanı şemasına güvenli bir şekilde uygulamak için kullanılan bir araçtır.
Temel Adımlar (Package Manager Console veya .NET CLI üzerinden):
- Migration Ekleme: Modelde bir değişiklik yaptıktan sonra, bu değişikliği uygulayacak bir migration betiği oluşturulur.
- PMC:
Add-Migration MigrationAdi
- CLI:
dotnet ef migrations add MigrationAdi
Bu komut, projenizde bir "Migrations" klasörü oluşturur ve içine C# koduyla yazılmış, değişiklikleri tanımlayan (Up
metodu) ve geri almayı sağlayan (Down
metodu) bir migration dosyası ekler.
- Veritabanını Güncelleme: Oluşturulan migration betiğini veritabanına uygular.
- PMC:
Update-Database
- CLI:
dotnet ef database update
Bu komut, bekleyen tüm migration'ları sırayla çalıştırarak veritabanı şemasını güncel hale getirir. Ayrıca veritabanı yoksa oluşturur.
- Migration Kaldırma (İsteğe Bağlı): Henüz veritabanına uygulanmamış son migration'ı kaldırmak için.
- PMC:
Remove-Migration
- CLI:
dotnet ef migrations remove
- Belirli Bir Migration'a Dönme (İsteğe Bağlı):
- PMC:
Update-Database MigrationAdi
- CLI:
dotnet ef database update MigrationAdi
Migrations kullanmak, veritabanı şemasını kodla birlikte versiyon kontrol sisteminde (Git vb.) takip etmeyi sağlar ve takım çalışmalarında veya farklı ortamlara (geliştirme, test, üretim) dağıtım yaparken şema uyumluluğunu yönetmeyi kolaylaştırır.
Web Geliştirme: ASP.NET Core
ASP.NET Core, .NET platformu üzerinde modern, bulut tabanlı, internete bağlı uygulamalar (web uygulamaları, API'ler, mikroservisler) oluşturmak için tasarlanmış açık kaynaklı, çapraz platform bir framework'tür. Yüksek performans, modülerlik ve esneklik sunar.
ASP.NET Core Mimarisi: Middleware ve İstek İşleme Hattı
ASP.NET Core uygulamalarında gelen her HTTP isteği, bir "istek işleme hattı" (request pipeline) üzerinden geçer. Bu hat, "middleware" adı verilen bileşenlerden oluşur. Her middleware, gelen isteği işleme, yanıtı değiştirme ve hattaki bir sonraki middleware'e devretme veya isteği kısa devre yaparak doğrudan yanıt döndürme yeteneğine sahiptir.
Middleware'ler Startup.cs
(veya modern .NET 6+ projelerinde Program.cs
) dosyasındaki Configure
metodu içinde yapılandırılır. Sıralamaları önemlidir, çünkü istekler ve yanıtlar bu sıraya göre işlenir.
Yaygın Middleware'ler:
- Exception Handling: Hataları yakalar ve kullanıcı dostu hata sayfaları gösterir.
- Static Files: HTML, CSS, JavaScript, resim gibi statik dosyaların sunulmasını sağlar.
- Routing: Gelen isteğin URL'sine göre hangi kodun (Endpoint - Controller Action, Razor Page, Minimal API handler) çalıştırılacağını belirler.
- Authentication: Kullanıcının kimliğini doğrular.
- Authorization: Kimliği doğrulanmış kullanıcının belirli kaynaklara erişim yetkisini kontrol eder.
- CORS (Cross-Origin Resource Sharing): Farklı bir origin'den (domain, protokol, port) gelen istemci tarafı isteklerine izin verilip verilmeyeceğini kontrol eder.
- HTTPS Redirection: Gelen HTTP isteklerini otomatik olarak HTTPS'e yönlendirir.
// Program.cs (ASP.NET Core 6+) Minimal Hosting Modeli Örneği
var builder = WebApplication.CreateBuilder(args);
// Servisleri ekleme (DI Container'a)
builder.Services.AddControllersWithViews(); // MVC için servisler
builder.Services.AddRazorPages(); // Razor Pages için servisler
var app = builder.Build();
// Middleware'leri yapılandırma (Sıralama Önemli!)
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error"); // Hata yönetimi (üretim)
app.UseHsts(); // HTTP Strict Transport Security
} else {
app.UseDeveloperExceptionPage(); // Geliştirme ortamı hata sayfası
}
app.UseHttpsRedirection(); // HTTPS yönlendirmesi
app.UseStaticFiles(); // Statik dosya sunumu (wwwroot klasörü)
app.UseRouting(); // Yönlendirme middleware'i
// app.UseCors(); // CORS (gerekirse)
// app.UseAuthentication(); // Kimlik doğrulama
app.UseAuthorization(); // Yetkilendirme
// Endpoint'leri (Controller, Razor Page vb.) eşleme
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"); // Varsayılan MVC rotası
app.MapRazorPages(); // Razor Pages için endpoint'ler
app.Run(); // Uygulamayı başlat
Uygulama Modelleri: MVC, Razor Pages, Web API, Minimal APIs
ASP.NET Core, farklı ihtiyaçlara yönelik çeşitli uygulama modelleri sunar:
- MVC (Model-View-Controller): Geleneksel ve yaygın kullanılan bir tasarım desenidir. Uygulamayı üç ana bileşene ayırır:
- Model: Uygulamanın verilerini ve iş mantığını temsil eder (genellikle Entity sınıfları ve iş kurallarını içeren servisler).
- View (Görünüm): Kullanıcı arayüzünü (HTML) oluşturur. Genellikle Razor sözdizimini kullanır. Verileri Model'den alır ve kullanıcıya gösterir.
- Controller (Denetleyici): Gelen istekleri alır, Model ile etkileşime girer (veriyi alır/işler) ve uygun View'ı seçerek kullanıcıya yanıtı döndürür.
Büyük, test edilebilir ve bakımı kolay uygulamalar için uygundur.
- Razor Pages: Sayfa odaklı bir yaklaşımdır. Her sayfa kendi
.cshtml
(Razor görünümü) ve .cshtml.cs
(PageModel - C# kodları ve iş mantığı) dosyasına sahiptir. MVC'ye göre daha basit ve daha hızlı geliştirme sağlayabilir, özellikle form tabanlı veya basit CRUD işlemleri içeren uygulamalar için uygundur.
- Web API: Kullanıcı arayüzü olmayan, genellikle HTTP üzerinden JSON veya XML formatında veri alışverişi yapan servisler oluşturmak için kullanılır. Tarayıcı tabanlı istemciler (SPA'lar - React, Angular, Vue), mobil uygulamalar veya diğer sunucu uygulamaları tarafından tüketilir. Genellikle Controller tabanlıdır (
ControllerBase
'den türetilir) ancak Minimal API'ler de kullanılabilir.
- Minimal APIs (.NET 6+): API endpoint'lerini çok daha az kodla, doğrudan
Program.cs
içinde tanımlamayı sağlayan yeni ve basit bir yaklaşımdır. Hızlı prototipleme, mikroservisler veya basit API'ler için idealdir. MVC'nin tüm özelliklerine ihtiyaç duyulmayan durumlar için daha hafiftir.
// Program.cs içinde Minimal API örneği
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Basit GET endpoint'i
app.MapGet("/", () => "Merhaba Minimal API!");
// Parametre alan GET endpoint'i
app.MapGet("/kullanici/{id}", (int id) => $"Kullanıcı ID: {id}");
// POST endpoint'i
app.MapPost("/urunler", (Urun urun) => {
// Gelen ürünü veritabanına ekleme vb.
Console.WriteLine($"Yeni ürün eklendi: {urun.Ad}");
return Results.Created($"/urunler/{urun.UrunId}", urun); // 201 Created yanıtı
});
app.Run();
// Kullanılacak Urun sınıfı (örnek)
public class Urun { public int UrunId { get; set; } public string Ad { get; set; } }
Bir ASP.NET Core projesinde bu modeller bir arada da kullanılabilir.
Bağımlılık Enjeksiyonu (Dependency Injection - DI)
DI, ASP.NET Core'un temel tasarım prensiplerinden biridir. Bir sınıfın (bağımlı sınıf) ihtiyaç duyduğu başka nesneleri (bağımlılıklar - dependencies) kendisinin oluşturması yerine, bu bağımlılıkların dışarıdan (genellikle bir DI konteyneri tarafından) sağlanmasıdır. Bu, "Inversion of Control" (Kontrolün Tersine Çevrilmesi - IoC) ilkesinin bir uygulamasıdır.
Faydaları:
- Gevşek Bağlılık (Loose Coupling): Sınıflar arasındaki doğrudan bağımlılıkları azaltır, bir sınıfın değiştirilmesi diğerlerini daha az etkiler.
- Test Edilebilirlik: Bağımlılıklar dışarıdan verildiği için, test sırasında gerçek bağımlılıklar yerine sahte (mock) nesneler enjekte edilerek sınıfın izole bir şekilde test edilmesi kolaylaşır.
- Tekrar Kullanılabilirlik ve Bakım Kolaylığı: Bileşenlerin değiştirilmesi ve yönetilmesi daha kolay hale gelir.
ASP.NET Core, yerleşik bir DI konteyneri ile gelir. Servisler (bağımlılıklar) Program.cs
(veya Startup.cs
'teki ConfigureServices
metodu) içinde konteynere kaydedilir ve ardından ihtiyaç duyan sınıfların yapıcı metotları (constructor injection - en yaygın yöntem) aracılığıyla otomatik olarak enjekte edilir.
Servis Yaşam Süreleri:
- Transient (Geçici): Servis her talep edildiğinde yeni bir örnek oluşturulur.
- Scoped (Kapsamlı): Her bir web isteği (request) başına tek bir örnek oluşturulur ve istek boyunca aynı örnek kullanılır.
- Singleton (Tekil): Uygulama ömrü boyunca sadece tek bir örnek oluşturulur ve her talepte aynı örnek kullanılır.
// Program.cs (veya Startup.cs) içinde servis kaydı
var builder = WebApplication.CreateBuilder(args);
// Servisleri kaydetme
builder.Services.AddDbContext(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // Scoped (varsayılan DbContext için)
builder.Services.AddScoped(); // Arayüz ve implementasyonunu kaydet (Scoped)
builder.Services.AddTransient(); // Transient
builder.Services.AddSingleton(); // Singleton
builder.Services.AddControllersWithViews();
var app = builder.Build();
// ... Middleware yapılandırması ...
app.Run();
// ----- Örnek Kullanım (Controller içinde Constructor Injection) -----
// Aşağıdaki IUrunRepository ve SqlUrunRepository tanımlamaları varsayımsaldır.
// public interface IUrunRepository { List TumunuGetir(); }
// public class SqlUrunRepository : IUrunRepository { /* Implementasyon */ public List TumunuGetir() { return new List(); } }
// public interface IEpostaGonderici { void Gonder(string kime, string konu, string mesaj); }
// public class SmtpEpostaGonderici : IEpostaGonderici { public void Gonder(string kime, string konu, string mesaj) { /* ... */ } }
// public interface IUygulamaAyarlari { string GetAyar(string anahtar); }
// public class UygulamaAyarlari : IUygulamaAyarlari { public string GetAyar(string anahtar) { return "deger"; } }
public class UrunlerController : Controller
{
private readonly IUrunRepository _urunRepository;
private readonly IEpostaGonderici _epostaGonderici;
// Bağımlılıklar constructor aracılığıyla enjekte edilir
public UrunlerController(IUrunRepository urunRepository, IEpostaGonderici epostaGonderici)
{
_urunRepository = urunRepository;
_epostaGonderici = epostaGonderici;
}
public IActionResult Index()
{
var urunler = _urunRepository.TumunuGetir();
// _epostaGonderici.Gonder(...); // Diğer bağımlılığı kullan
return View(urunler);
}
}
İleri C# ve .NET Konuları
Temelleri ve web geliştirmeyi öğrendikten sonra, daha karmaşık problemler çözmek ve daha verimli kod yazmak için bazı ileri konulara hakim olmak faydalıdır.
Asenkron Programlama (async
, await
, Task
)
Modern uygulamalarda, özellikle I/O (Input/Output) işlemleri (ağ istekleri, veritabanı erişimi, dosya okuma/yazma) gibi zaman alan operasyonların uygulamanın ana iş parçacığını (thread) bloke etmemesi çok önemlidir. Asenkron programlama, bu tür operasyonların arka planda çalışmasına izin verirken, uygulamanın yanıt vermeye devam etmesini sağlar.
C#'ta asenkron programlama async
ve await
anahtar kelimeleri ile Task Tabanlı Asenkron Desen (Task-based Asynchronous Pattern - TAP) kullanılarak yapılır:
async
: Bir metodun asenkron olduğunu ve içinde await
kullanılabileceğini belirtir. async
metotlar genellikle Task
, Task
veya void
(olay işleyicileri dışında önerilmez) döndürür.
await
: Bir Task
veya Task
döndüren asenkron bir metodun önüne yazılır. Programın akışını, beklenen işlem tamamlanana kadar bloke etmeden duraklatır. İşlem tamamlandığında metot kaldığı yerden devam eder. Eğer beklenen Task bir sonuç döndürüyorsa (Task
), await
bu sonucu (TResult
tipinde) döndürür.
Task
: Bir sonuç döndürmeyen asenkron bir işlemi temsil eder.
Task
: TResult
tipinde bir sonuç döndüren asenkron bir işlemi temsil eder.
EF Core, HttpClient gibi birçok modern .NET kütüphanesi asenkron metotlar (genellikle sonunda 'Async' eki bulunur) sunar.
using System.Net.Http;
using System.Threading.Tasks; // Task için gerekli
public class AsenkronOrnek
{
private static readonly HttpClient client = new HttpClient();
// Task döndüren asenkron metot
public async Task VeriCekAsync(string url)
{
Console.WriteLine($"'{url}' adresinden veri çekiliyor (async)...");
try
{
// await ile ağ isteğinin tamamlanmasını bekle
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode(); // Başarısızsa hata fırlatır
// await ile içeriğin okunmasını bekle
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"'{url}' adresinden veri çekme tamamlandı.");
return responseBody;
}
catch (HttpRequestException e)
{
Console.WriteLine($"\nİstek Hatası: {e.Message}");
return null;
}
}
// Task döndüren (sonuçsuz) asenkron metot
public async Task IslemleriBaslatAsync()
{
Console.WriteLine("Asenkron işlemler başlıyor...");
// await ile ilk işlemin tamamlanmasını bekle
string sonuc1 = await VeriCekAsync("https://jsonplaceholder.typicode.com/todos/1");
if(sonuc1 != null)
Console.WriteLine($"Sonuç 1 (ilk 50 karakter): {sonuc1.Substring(0, Math.Min(50, sonuc1.Length))}...");
// await ile ikinci işlemin tamamlanmasını bekle
string sonuc2 = await VeriCekAsync("https://jsonplaceholder.typicode.com/posts/1");
if(sonuc2 != null)
Console.WriteLine($"Sonuç 2 (ilk 50 karakter): {sonuc2.Substring(0, Math.Min(50, sonuc2.Length))}...");
// Paralel çalıştırma örneği (Task.WhenAll ile)
Console.WriteLine("\nİki işlemi paralel başlatma:");
Task task3 = VeriCekAsync("https://jsonplaceholder.typicode.com/todos/2");
Task task4 = VeriCekAsync("https://jsonplaceholder.typicode.com/posts/2");
// İki task'in da tamamlanmasını bekle
await Task.WhenAll(task3, task4);
Console.WriteLine("Paralel işlemler tamamlandı.");
Console.WriteLine($"Sonuç 3: {(task3.Result != null ? "Var" : "Yok")}, Sonuç 4: {(task4.Result != null ? "Var" : "Yok")}");
Console.WriteLine("Tüm asenkron işlemler bitti.");
}
}
// Kullanım:
// AsenkronOrnek ornek = new AsenkronOrnek();
// await ornek.IslemleriBaslatAsync(); // Eğer çağıran metot da async ise
// VEYA
// ornek.IslemleriBaslatAsync().Wait(); // Ana thread'i bloke eder (genellikle kaçınılır)
// VEYA
// Task t = ornek.IslemleriBaslatAsync();
// t.GetAwaiter().GetResult(); // Ana thread'i bloke eder
async/await
kullanmak, özellikle UI uygulamalarında arayüzün donmasını ve sunucu uygulamalarında thread kaynaklarının verimli kullanılmasını sağlar.
Diğer İleri Konular (Kısa Bahis)
- Threading ve Paralel Programlama: CPU'yu daha verimli kullanmak için işlemleri birden fazla iş parçacığında (thread) veya paralel olarak çalıştırma teknikleri (
System.Threading
, Task Parallel Library - TPL).
- Reflection: Çalışma zamanında (runtime) kodun yapısını (tipler, metotlar, özellikler vb.) inceleme ve dinamik olarak kod çağırma yeteneği.
- Attributes (Nitelikler): Koda meta veriler eklemek için kullanılan bildirimsel etiketler (
[Serializable]
, [Obsolete]
, [Required]
gibi). Reflection ile okunabilirler.
- NuGet Paket Yönetimi: .NET projelerine üçüncü parti kütüphaneleri (paketleri) kolayca eklemek, güncellemek ve yönetmek için kullanılan sistem.
- Generics (Genel Türler): Belirli bir tipe bağlı olmayan, tür güvenli ve yeniden kullanılabilir sınıflar, metotlar ve arayüzler oluşturmayı sağlar (örn:
List
, Dictionary
).
- SOLID Prensipleri: Daha anlaşılır, esnek ve bakımı kolay nesne yönelimli tasarımlar oluşturmak için kullanılan beş temel prensip (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion).
- Unit Testing / Integration Testing: Kodun doğruluğunu otomatik olarak kontrol etmek için testler yazma (xUnit, NUnit, MSTest gibi framework'ler ve Moq, NSubstitute gibi mocking kütüphaneleri ile).
Sonuç: C# ve .NET ile Geliştirme
C# dili ve .NET platformu, modern yazılım geliştirmenin zorluklarına cevap veren güçlü, esnek ve sürekli gelişen bir ekosistem sunar. Tip güvenliği, nesne yönelimli yapısı, zengin kütüphaneleri, çapraz platform desteği ve performans odaklı mimarisi ile çok çeşitli uygulamalar geliştirmek için sağlam bir temel sağlar.
Bu rehber, C# ve .NET dünyasına bir giriş yaparak temel kavramları, önemli teknolojileri ve modern yaklaşımları ele almıştır. Ancak bu geniş ekosistemde öğrenilecek her zaman daha fazla şey vardır. ASP.NET Core ile web, .NET MAUI ile mobil, Entity Framework Core ile veri erişimi gibi alanlarda uzmanlaşmak veya LINQ, asenkron programlama gibi dil özelliklerini derinlemesine öğrenmek mümkündür.
Microsoft Learn, .NET belgeleri (docs.microsoft.com/dotnet), topluluk blogları ve açık kaynak projeler, öğrenme sürecinde değerli kaynaklardır. Sürekli pratik yapmak, projeler geliştirmek ve yeni özellikleri takip etmek, C# ve .NET geliştiricisi olarak yeteneklerinizi geliştirmenin anahtarıdır. Bu güçlü araçlarla, fikirlerinizi etkili ve ölçeklenebilir yazılım çözümlerine dönüştürebilirsiniz.