Bölüm 12 / 13
Modern C#
Pattern matching, switch expression, top-level statements, primary constructor.
13 dakikalık okuma
Bu hattın 2. dersi is null / is not
null kalıbını, 4. dersi catch ... when süzgecini kullanmış ve
"bunlar desen eşleştirmenin parçası, mekanizmasını 12. ders açacak" demişti. Şimdi
açıyoruz. Ders iki katmanlıdır: birincisi desen eşleştirme ve
switch ifadeleri — C#'ın son on yıldaki en büyük ifade gücü artışı;
ikincisi kodu kısaltan sözdizimi katmanı. Ama asıl mesele sözdizimi listesi
ezberlemek değil: hangi kısaltma okunabilirliği artırır, hangisi gizler
sorusunu cevaplayabilmek. Kod incelemelerinde en çok tartışılan başlık budur.
01Ne öğreneceksin
- Desen eşleştirme ailesi: tip, sabit, özellik, ilişkisel, mantıksal, demet ve liste desenleri
switchifadesi: deyimden farkı, tamlık denetimi,whenkoşulu- Gürültüyü azaltan sözdizimi: en üst düzey ifadeler, dosya kapsamlı namespace, global using, hedef tipli
new, birincil yapıcı, koleksiyon ifadeleri - Sürüm haritası: C# 8'den 14'e neyin ne zaman geldiği ve
LangVersion - Tuzaklar: birincil yapıcının record'dan farkı, aşırı kısaltmanın maliyeti
- Eski kodu modernleştirme ve ekip kararını
.editorconfigile yazıya dökme
02Desen eşleştirme
Desen (pattern), bir değerin "şu biçimde mi" diye sınandığı ve aynı anda
parçalarına ayrıldığı ifadedir. Hat 01'in if dersinde yazdığın kontrol
zincirlerinin ve tip dönüşümlerinin yerine geçer. Aile şöyle büyüdü:
| Desen | Örnek | Anlamı |
|---|---|---|
| Tip / bildirim | o is Musteri m | Tip uyuyorsa ve değişkene atarsa |
| Sabit | x is 0, s is "iptal" | Belirli bir değere eşitse |
| null | x is null / is not null | Null denetimi (2. dersin kalıbı) |
| Özellik | s is { Tutar: > 1000, Durum: "acik" } | Alanları birlikte sınar; iç içe yazılabilir |
| İlişkisel | yas is >= 18 | Karşılaştırma operatörü desen içinde |
| Mantıksal | is >= 18 and < 65, is not null | and / or / not ile birleştirme |
| Demet (tuple) | (rol, tutar) switch { ... } | Birden çok değeri tek kararda sınar |
| Liste | dizi is [1, .., var son] | Dizinin biçimini sınar, parçasını yakalar |
Kazancı somut görmek için, Hat 01'de yazdığın tarzda bir kontrolü karşılaştır:
// eski
if (nesne != null && nesne is Siparis)
{
var s = (Siparis)nesne;
if (s.Tutar > 1000 && s.Durum == "acik") IndirimUygula(s);
}
// desenlerle: tek ifade, tek okuma
if (nesne is Siparis { Tutar: > 1000, Durum: "acik" } s) IndirimUygula(s);
Buradaki üç kazanç, dersin özüdür: null denetimi desene dahildir (tip
deseni null'ı zaten eler), dönüştürme ile denetim tek adımdır (ayrı bir
cast yok — 1. dersteki geçersiz dönüşüm riski kalkar) ve koşul
okunduğu gibidir.
not operatörünün 2. dersteki özel değeri hatırlanmalı:
if (x is not null), if (x != null)'dan daha güvenlidir —
çünkü != operatörü tip tarafından ezilebilir, desen ise
ezilemez; her zaman gerçek null denetimi yapar.
03switch ifadesi
Klasik switch bir deyimdir (statement): dallanır, iş yapar.
C# 8'in switch ifadesi (expression) ise değer üretir:
// deyim: 12 satır, break'ler, geçici değişken
string etiket;
switch (siparis.Durum)
{
case "acik": etiket = "Açık"; break;
case "kapali": etiket = "Kapalı"; break;
default: etiket = "Bilinmiyor"; break;
}
// ifade: 5 satır, atama tek yerde, derleyici tamlığı denetler
string etiket = siparis.Durum switch
{
"acik" => "Açık",
"kapali" => "Kapalı",
_ => "Bilinmiyor" // _ : her şeyi yakalayan desen
};
Asıl güç, desenlerle birleşince ortaya çıkar. Aşağıdaki örnek, iş kurallarını bir tabloya çevirir — Hat 04'te göreceğin "koşul karmaşasını veriye çevirme" fikrinin dildeki karşılığıdır:
decimal Komisyon(Musteri m, decimal tutar) => (m.Segment, tutar) switch
{
("kurumsal", >= 100_000) => tutar * 0.02m,
("kurumsal", _) => tutar * 0.03m,
("bireysel", > 0) when m.YilSayisi >= 5 => tutar * 0.04m, // when: desenin yetmediği yerde koşul
("bireysel", > 0) => tutar * 0.05m,
_ => throw new ArgumentOutOfRangeException(nameof(tutar))
};
Üç davranış kuralını bilmek gerekir:
- Sıra önemlidir. Kollar yukarıdan aşağıya denenir; ilk eşleşen kazanır. Daha genel bir deseni yukarı koyarsan alttakiler ölü kod olur — derleyici bunu genelde uyarı olarak söyler.
- Tamlık (exhaustiveness) denetlenir. Derleyici, kapsanmayan bir değer
olabileceğini görürse uyarır. Hiçbir kol eşleşmezse çalışma zamanında
SwitchExpressionExceptionfırlar — sessizce yanlış bir değer dönmez, bu iyi bir şeydir. whensüzgeci, desenin ifade edemediği koşullar içindir (başka bir nesneye bakmak, bir metot çağırmak). 4. derstekicatch (X ex) when (...)tam olarak aynı mekanizmadır — o dersin senedi burada kapanıyor:when, hemswitchkollarında hemcatchbloklarında "bu dalı seç" kararının süzgecidir.
04Gürültüyü azaltan sözdizimi
İkinci katman, aynı kodu daha az tören ile yazmayı sağlar. Faydası tek tek küçük, toplamı büyüktür:
// —— Program.cs, en üst düzey ifadeler (top-level statements, C# 9) ——
// class Program / static void Main sarmalı yok:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run();
// —— dosya kapsamlı namespace (C# 10): bir seviye girinti kazanılır ——
namespace Sirket.Siparis; // süslü parantez yok
// —— global using (C# 10): her dosyada tekrarlanan using'ler tek yerde ——
global using System.Text.Json;
// —— hedef tipli new (C# 9): tip zaten solda yazılı, sağda tekrarlama ——
private readonly Dictionary<string, List<int>> _kutu = new();
// —— koleksiyon ifadeleri (C# 12) ——
int[] sayilar = [1, 2, 3];
List<string> adlar = [ilk, .. digerleri, "son"]; // .. yayma (spread)
// —— ham metin değişmezleri (C# 11): kaçış işaretsiz JSON/SQL ——
string sorgu = """
SELECT Ad, Soyad FROM Musteri
WHERE Segment = "kurumsal"
""";
Birincil yapıcı (primary constructor, C# 12) ayrı bir başlığı hak eder, çünkü en çok yanlış anlaşılan özelliktir. Sınıf adının yanına yazılan parametreler sınıfın tüm gövdesinde kullanılabilir:
public class SiparisServisi(ISiparisDeposu depo, ILogger<SiparisServisi> log)
{
public async Task<Siparis?> GetirAsync(int id, CancellationToken token)
{
log.LogInformation("Sipariş {Id} isteniyor", id); // parametreler doğrudan kullanılıyor
return await depo.BulAsync(id, token);
}
}
Bu, bağımlılık enjeksiyonu yazan sınıflarda (Hat 05'in her servisi) alan tanımı + yapıcı + atama üçlüsünü tek satıra indirir. Ama record'la karıştırma — 10. dersle çapraz sorulan fark:
record birincil yapıcısı | class birincil yapıcısı | |
|---|---|---|
| Özellik üretir mi? | Evet — public init özellikler | Hayır — yalnız gövdede görünür parametre |
| Dışarıdan okunur mu? | Evet | Hayır (kullanılıyorsa gizli bir alana yakalanır) |
| Değer eşitliği? | Evet | Hayır |
| Değiştirilebilir mi? | Hayır (init) | Evet — parametreye gövdeden atama yapılabilir |
Son satır pratik bir uyarıdır: birincil yapıcı parametresi readonly
değildir. Değişmezliği garanti etmek istiyorsan klasik yolu kullan:
private readonly ISiparisDeposu _depo = depo;
05Sürüm haritası
Mülakatta "hangi sürümle geldi" sorulur; sahada ise "bu proje bunu kullanabilir mi" sorulur. İkisinin cevabı aynı tablodadır:
| Sürüm | Platform | Öne çıkanlar |
|---|---|---|
| C# 8 | .NET Core 3.x | Nullable referans tipleri (2. ders), switch ifadesi, using bildirimi, aralık/indeks ([..3]), IAsyncEnumerable, varsayılan arayüz metotları |
| C# 9 | .NET 5 | record, init, en üst düzey ifadeler, ilişkisel ve mantıksal desenler, hedef tipli new |
| C# 10 | .NET 6 | Dosya kapsamlı namespace, global using, record struct, genişletilmiş özellik desenleri |
| C# 11 | .NET 7 | Ham metin değişmezleri, required, liste desenleri, arayüzlerde statik soyut üyeler (generic matematik) |
| C# 12 | .NET 8 | Birincil yapıcılar, koleksiyon ifadeleri [..], tip takma adları, varsayılan lambda parametreleri |
| C# 13 | .NET 9 | params koleksiyonları, yeni Lock tipi, iterator/async içinde ref kullanımı |
| C# 14 | .NET 10 | Extension üyeleri (11. ders), field anahtar kelimesi, null koşullu atama |
Üç pratik kural:
- Dil sürümü, proje dosyasındaki
<TargetFramework>'e göre otomatik seçilir.<LangVersion>ile elle yükseltmek genelde yanlıştır: bazı özellikler yalnız sözdizimidir, bazıları çalışma zamanı desteği ister (record'uninit'i eski hedeflerde çalışmaz). - Kurumsal projelerde LTS sürümleri tercih edilir; kurumun desteklediği çerçeveyi bilmek işin bir parçasıdır. "En yeni özelliği kullanayım" değil, "hedef çerçeve ne veriyor" sorusu doğru sorudur.
- Yeni dil özellikleri, eski API'leri emekliye ayırmaz. Bir kod tabanında iki biçimin de bulunması normaldir; toplu biçim değişikliği ile davranış değişikliğini aynı commit'e koyma — inceleme yapan kişi ikisini ayıramaz.
06Kısalık ne zaman zarar verir
Bu bölüm dersin asıl kıdem katmanıdır: her yeni özellik bir kazanç değildir. Kod incelemesinde tartışılan başlıklar:
- Uzun
switchifadeleri. Beş kolun ötesine geçen, iç içe desenler içeren vewhen'lerle dolan bir ifade, okunmayan tek bir bloktur. Kural: kollar tek satırlık eşlemeler olduğu süreceswitchifadesi; her kol iş yapmaya başladığında ayrı metot ya da polimorfizm (Hat 01'in 13. dersi, Hat 04'ün Strategy deseni). varkullanımı. Tip sağdan açıkça okunuyorsa (var m = new Musteri()) sorun yok; bir metot çağrısının dönüşünde (var x = Hesapla()) okuyucudan tanıma gitmesini istemiş olursun. Ekip kararıdır ve.editorconfig'e yazılır.- Aşırı ifadeleştirme. Üç iç içe üçlü operatör
(
?:) ya da tek satıra sıkıştırılmış LINQ + desen karışımları, "az satır" ile "az anlaşılan"ı karıştırır. Ölçüt satır sayısı değil, bir kez okumada anlaşılmadır. - En üst düzey ifadeler yalnız giriş noktası içindir.
Program.cs'de değerlidir; iş mantığını oraya yığmak, yapılandırılmamış bir betik üretir. - Liste desenlerinin maliyeti.
dizi is [_, _, var son]okunaklıdır ama sayılamayan (lazy) kaynaklarda uzunluk bilgisi gerektirir; sıcak yolda ölçmeden kullanma — 6. dersin ertelenmiş çalışma uyarısı burada da geçerlidir.
Karşı tarafta, tereddütsüz benimsenen özellikler de vardır:
is not null, using bildirimi, dosya kapsamlı namespace,
nameof, string interpolation ve init. Bunlar hem daha kısa
hem daha güvenlidir; "eski tarz" yazmanın savunulabilir bir gerekçesi yoktur.
07Sahada: eski kodu modernleştirmek
Gerçek işte modern C#'ı sıfırdan yazmazsın; yıllardır yaşayan bir kod tabanına taşırsın. İzlenen sıra:
- Kararı yazıya dök.
.editorconfigdosyası, ekibin biçim ve tarz kararlarını makine tarafından uygulanabilir hâle getirir (csharp_style_prefer_switch_expression,csharp_style_var_when_type_is_apparentgibi). Tartışma bir kez yapılır, sonra araç uygular. - Analizörlere güven. Derleyici ve .NET analizörleri "burada
switchifadesi kullanılabilir", "buradausingbildirimi olur" diye önerir; editör tek tuşla uygular. Elle tarama yapma. - Önce güvenlik kazandıranlar. Modernleştirme sırası keyfi değildir:
nullable referans tiplerini açmak (2. ders),
is not null'a geçmek veusingbildirimlerini uygulamak hata azaltır; birincil yapıcı ve koleksiyon ifadeleri yalnız kısaltır. Önce ilk grup. - Biçim commit'ini ayır. Yüzlerce dosyayı biçimlendiren bir değişiklik,
aynı commit'te bir hata düzeltmesi taşıyorsa incelenemez. Hat 16'da bunun
araçlarını (
git blamegürültüsü ve yok sayma dosyası) göreceksin.
Bu ders, hattın son öğretim dersidir. Sırada Hat 03'ün yeterlilik kontrolü var: 12 dersin hepsini tek bir gerçekçi görevde kullanacaksın — tip kararlarından null güvenliğine, LINQ'dan async'e, kaynak yönetiminden desen eşleştirmeye kadar.
08Mini alıştırma — Koşul zincirini tabloya çevir
Yaklaşık 20 dakika · dotnet run
- Desene çevir:
objectalan ve tipine göre farklı metin döndüren bir metodu önce klasikif+is+castile, sonra tek birswitchifadesiyle yaz (int,string,null,List<int>ve "diğer" kollarıyla). İki sürümün satır sayısını ve okunabilirliğini karşılaştır. - Özellik ve ilişkisel desen:
Siparis(decimal Tutar, string Durum, int MusteriYili)record'u tanımla; kargo ücretiniswitchifadesiyle hesapla — 1000 üstü ücretsiz, açık siparişte indirimli, 5 yıldan eski müşteride farklı. En az bir koldawhenkullan. - Tamlık deneyi: yukarıdaki
switch'ten_kolunu sil ve derle — derleyici ne diyor? Uyarıya rağmen çalıştırıp kapsanmayan bir değer ver: hangi istisnayı aldın? - Liste deseni:
int[]alan bir metot yaz;[](boş),[var tek],[var ilk, .., var son]kollarıyla farklı metinler döndürsün. 0, 1, 2 ve 5 elemanlı dizilerle dene. - Birincil yapıcı farkı: aynı iki parametreyle bir
recordve birclasstanımla. Her ikisinde de parametreye dışarıdan erişmeyi dene — hangisi derlendi?classsürümünde bir metottan parametreye atama yap: derlendi mi? Bulgularını 10. dersin tablosuyla karşılaştır.
09Kendini sına
Desen eşleştirme nedir, hangi desen türleri vardır? "x is not null" ile "x != null" arasındaki fark nedir?
Desen eşleştirme, bir değerin belirli bir biçime uyup uymadığını sınayan ve aynı anda parçalarını değişkene bağlayan ifadedir; tip denetimi, dönüştürme ve null kontrolünü tek adıma indirir. Türleri: tip/bildirim deseni (o is Musteri m), sabit deseni, null deseni, özellik deseni ({ Tutar: > 1000 }), ilişkisel desen (is >= 18), mantıksal desenler (and/or/not), demet deseni ve liste deseni ([1, .., var son]). Tip deseni null'ı zaten eler, bu yüzden ayrı null kontrolü gerekmez. "is not null" ile "!= null" arasındaki fark güvenliktir: != operatörü tip tarafından ezilebilir ve beklenmedik bir davranış üretebilir; desen ezilemez, her zaman gerçek null denetimi yapar.
switch ifadesi ile switch deyimi arasındaki fark nedir? Kol sırası, tamlık ve when nasıl çalışır?
switch deyimi dallanır ve iş yapar; switch ifadesi bir değer üretir, dolayısıyla doğrudan atanabilir veya döndürülebilir — break, geçici değişken ve tekrarlanan atama ortadan kalkar. Kollar yukarıdan aşağıya denenir ve ilk eşleşen kazanır; daha genel bir deseni yukarı koymak alttakileri ölü koda çevirir, derleyici bunu genelde uyarır. Derleyici tamlığı denetler: kapsanmayan değer kalabiliyorsa uyarı verir, çalışma zamanında hiçbir kol eşleşmezse SwitchExpressionException fırlar — sessizce yanlış değer dönmez. when süzgeci, desenin ifade edemediği ek koşullar içindir (başka bir nesneye bakmak, metot çağırmak); aynı mekanizma catch bloklarında exception filter olarak kullanılır. Pratik sınır: kollar tek satırlık eşlemeler olduğu sürece switch ifadesi okunaklıdır, her kol iş yapmaya başladığında ayrı metot veya polimorfizm gerekir.
Sınıftaki birincil yapıcı ile record'un birincil yapıcısı arasındaki farklar nelerdir?
record'un birincil yapıcı parametreleri public init-only özelliklere dönüşür: dışarıdan okunabilir, değer eşitliğine katılır, ToString'de görünür ve kurulumdan sonra değiştirilemez. Sınıfın birincil yapıcı parametreleri (C# 12) ise özellik üretmez: yalnız sınıf gövdesinde görünür, dışarıdan erişilemez, değer eşitliği doğurmaz ve kullanıldıklarında derleyici tarafından gizli bir alana yakalanırlar. Ayrıca readonly değildirler — gövdeden atama yapılabilir, yani istemeden değiştirilebilir durum oluşur. Değişmezlik garantisi isteniyorsa parametre klasik biçimde bir readonly alana kopyalanır (private readonly IDepo _depo = depo;). Sınıf birincil yapıcısının asıl değeri, bağımlılık enjeksiyonu alan servislerde alan-yapıcı-atama üçlüsünü tek satıra indirmesidir.
Yeni dil özellikleri her zaman kullanılmalı mı? Eski bir kod tabanını modernleştirirken hangi sırayı izlersin?
Hayır — ölçüt satır sayısı değil, bir kez okumada anlaşılma. Tereddütsüz benimsenenler hem kısa hem güvenli olanlardır: is not null, using bildirimi, dosya kapsamlı namespace, nameof, string interpolation, init. Dikkatle kullanılanlar: uzun ve when'lerle dolmuş switch ifadeleri, iç içe üçlü operatörler, iş mantığını taşıyan en üst düzey ifadeler, sıcak yolda liste desenleri. Ayrıca dil sürümü hedef çerçeveye bağlıdır; LangVersion'ı elle yükseltmek bazı özellikler çalışma zamanı desteği gerektirdiği için yanlıştır ve kurumsal projelerde LTS çerçevesi belirleyicidir. Modernleştirme sırası: önce ekip kararını .editorconfig'e yazmak, sonra analizör önerilerine güvenmek, önce hata azaltan dönüşümleri (nullable referans tipleri, is not null, using bildirimi) uygulamak, en sona yalnız kısaltan dönüşümleri bırakmak; ve biçim değişikliği ile davranış değişikliğini asla aynı commit'e koymamak.
10Sınav köşesi
Yazılıda nasıl sorulur
(1) Kavram: "Pattern matching nedir?", "switch ifadesi ile
deyimi arasındaki fark". (2) Sözdizimi eşleme: verilen desenin ne sınadığını
yazma (is { Tutar: > 1000 }, is >= 18 and < 65,
is [_, .., var son]). (3) Çıktı sorusu: kol sırası değiştirilmiş
bir switch ifadesinde hangi kolun çalıştığı. (4) Hangi sürüm:
record (C# 9), dosya kapsamlı namespace (C# 10), ham metin
değişmezleri ve required (C# 11), birincil yapıcı ve koleksiyon
ifadeleri (C# 12). (5) Doğru/yanlış: "sınıfın birincil yapıcı parametreleri
public özellik üretir" (yanlış — record'da üretir). (6) when'in iki
kullanımı (switch kolu ve catch süzgeci — Hat 03'ün
4. dersiyle çapraz). (7) Kod modernleştirme: verilen eski bloğu desenli sürüme
çevirme.
Sözlüde nasıl anlatılır — 2 dakikalık taslak
"Modern C# ile ne değişti" için düzen: (1) Çekirdek — "en büyük değişim desen
eşleştirme: tip denetimi, dönüştürme ve null kontrolü tek ifadeye indi; koşul
zincirleri switch ifadesiyle okunur bir karar tablosuna dönüştü."
(2) İkinci katman — "tören azaltan sözdizimi: en üst düzey ifadeler, dosya
kapsamlı namespace, global using, hedef tipli new,
birincil yapıcılar, koleksiyon ifadeleri." (3) Sürüm bilinci — "dil sürümü hedef
çerçeveye bağlıdır; kurumsal projede LTS ve LangVersion'a elle
dokunmama disiplini vardır." (4) Kıdem vuruşu — "her yeni özelliği kullanmam;
ölçüt bir kez okumada anlaşılmaktır. is not null ve
using bildirimini tereddütsüz kullanırım çünkü hata azaltırlar; ama
when'lerle şişmiş bir switch'i polimorfizme çeviririm.
Modernleştirmeyi analizörlerle yapar, biçim commit'ini davranış commit'inden
ayırırım."
