C# 13 ile .NET 9 dünyasına gelen yenilikleri inceliyoruz: params collections, yeni lock tipi, partial property ve indexer, method group natural type, implicit index access, escape sequence \e ve daha fazlası — bolca örnekle.
24 Nisan 2026 7 dk okuma 23 0
Merhaba arkadaşlar, bu makalemizde uzun zamandır takip ettiğim C# dilinin en yeni versiyonu C# 13'ün getirdiği yenilikleri detaylı bir şekilde inceliyor olacağız. C# 13, .NET 9 ile birlikte 2024 yılının sonunda yayınlandı ve daha önceki versiyonların aksine bu sefer hem günlük hayatta işe yarayacak pratik küçük dokunuşlar, hem de performans ve güvenlik tarafında ciddi değişiklikler var. Başlamadan önce bir kahve alın, çünkü bol kod örneği olacak.
Kurulum
C# 13 özelliklerini kullanabilmek için makinenizde .NET 9 SDK'nın kurulu olması gerekiyor. Projenin .csproj dosyasında şu ayarın olduğunu doğrulayın:
Aslında net9.0 hedefliyorsanız LangVersion'u açıkça yazmanıza gerek yok, derleyici otomatik olarak C# 13'ü kullanır. Ama farklı target framework'lerde test ediyorsanız elle belirlemeniz gerekebilir.
1. Params Collections — IEnumerable, Span, ReadOnlySpan ve Daha Fazlası
Klasik params anahtar kelimesi yıllardır T[] (array) ile çalışıyordu. C# 13 ile birlikte artık bu kısıtlama kaldırıldı. Params'ı IEnumerable, List, Span, ReadOnlySpan ve IEnumerable implement eden diğer tipler için de kullanabiliyoruz.
Eskiden şöyle yazıyorduk:
void Topla(params int[] sayilar)
{
int toplam = 0;
foreach (var s in sayilar) toplam += s;
Console.WriteLine(toplam);
}
Topla(1, 2, 3, 4, 5); // int[] allocate edilir
Visual Studio 2022 - switch expression IntelliSense, pattern onerilerini listeliyor.
IDE tarafinda: Visual Studio tarafinda bu ornekleri yazarken pattern editor'unun IntelliSense'i size switch arm'leri icin otomatik oneriler sunuyor. Ctrl+Boslik ile listeyi gorebilirsiniz.
Her çağrıda heap üzerinde yeni bir array oluşturuluyordu. Kısa ömürlü method'larda bu performansı düşüren bir durum. Şimdi:
void Topla(params ReadOnlySpan<int> sayilar)
{
int toplam = 0;
foreach (var s in sayilar) toplam += s;
Console.WriteLine(toplam);
}
Topla(1, 2, 3, 4, 5); // stack üzerinde span, allocation yok!
ReadOnlySpan<int> stack tabanlı olduğu için heap allocation olmuyor. Bu özellikle framework kodlarında ve sıkı performans gerektiren uygulamalarda çok büyük kazanç sağlıyor. Ayrıca aynı method'u IEnumerable ile de overload edebilirsiniz:
void Yazdir(params IEnumerable<string> items)
{
foreach (var s in items) Console.WriteLine(s);
}
var liste = new List<string> { "a", "b", "c" };
Yazdir("tek", "iki", "üç");
Yazdir(liste); // artık doğal olarak çalışır
2. Yeni Lock Tipi — System.Threading.Lock
Bu benim en sevdiğim yenilik. Şimdiye kadar çoklu thread senaryolarında lock almak için şöyle yazıyorduk:
private readonly object _gate = new object();
public void Islem()
{
lock (_gate)
{
// thread-safe işlemler
}
}
Buradaki problem şu: object tipi aslında "monitor pattern" için tasarlanmamıştı, dil yıllar içinde buna zorlandı. Yanlışlıkla başka bir thread'in de aynı object'e lock alması, veya object'in başka bir yerde kullanılması gibi sorunlar olabiliyordu.
C# 13 ile System.Threading.Lock adında özel bir tip geldi:
using System.Threading;
private readonly Lock _gate = new Lock();
public void Islem()
{
lock (_gate)
{
// thread-safe işlemler
}
}
Görünüşte değişiklik yok ama derleyici artık Lock tipini özel şekilde işliyor. lock keyword'u Lock'un EnterScope() metodunu çağırıyor ve disposable pattern ile kilit bırakılıyor. Bu:
- Daha hızlı
- Yanlış kullanıma karşı daha güvenli (Monitor.Enter yerine tipe özel mekanizma)
- Kod niyetini daha net ifade ediyor (bu obje zaten lock içindir)
Yeni kod yazıyorsanız artık new object() yerine new Lock() kullanın.
3. Partial Property ve Indexer
Partial class ve method yıllardır vardı. C# 13 ile birlikte artık partial property ve partial indexer'ımız da var. Source generator yazanlar veya büyük projelerde kod üretim tarafında çalışanlar için çok değerli:
// ViewModel.cs (developer yazıyor)
public partial class KullaniciViewModel
{
public partial string Ad { get; set; }
public partial string SoyadUppercase { get; }
}
// ViewModel.g.cs (source generator üretiyor)
public partial class KullaniciViewModel : INotifyPropertyChanged
{
private string _ad = "";
public partial string Ad
{
get => _ad;
set
{
if (_ad == value) return;
_ad = value;
OnPropertyChanged(nameof(Ad));
}
}
public partial string SoyadUppercase => Ad?.ToUpperInvariant() ?? "";
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string n) => PropertyChanged?.Invoke(this, new(n));
}
MVVM, ORM, serializer gibi kütüphanelerin kod üretim iş yükü bu sayede çok sadeleşiyor.
4. Method Group Natural Type İyileştirmesi
C# 13 öncesinde method grup atamalarında tip çıkarımı bazen canınızı sıkardı. Örneğin:
string Kucult(string s) => s.ToLower();
// Eskiden:
var fn = Kucult; // "is a method group" hatası alabilirdiniz
// Şimdi:
var fn = Kucult; // Func<string, string> olarak otomatik çıkarılır
Özellikle LINQ sorgularında overload çözümünde bu iyileştirme derleyiciye daha akıllıca davranmasını söylüyor. Eskiden çaresiz kalıp açıkça Func<string, string> fn = Kucult; yazıyordunuz, artık var ile de çalışıyor.
5. Object Initializer'da Implicit Index — ^
Collection'lar üzerinde son elemana erişmek için ^1 kullanıyorduk. Şimdi object initializer içinde de kullanılabiliyor:
public class Buffer
{
public int[] Data { get; set; } = new int[5];
}
// C# 13 öncesi: initializer içinde ^ kullanılmıyordu
var b = new Buffer();
b.Data[^1] = 99;
// C# 13 ile:
var b = new Buffer
{
Data =
{
[^1] = 99,
[^2] = 88
}
};
Küçük bir özellik ama bazı fluent konfigürasyon senaryolarında çok işe yarıyor.
6. Yeni Escape Sequence: \e (ESC karakteri)
Terminal uygulamaları yazanların hayatı kolaylaştı. ANSI escape kodları için sürekli "\u001b" yazmaktan kurtulduk:
Küçük bir syntactic sugar ama terminal emulator projelerinde onlarca satırlık ANSI kod yazan biri olarak bu değişiklik çok kıymetli.
7. Ref Struct Interfaces (Experimental)
Bu özellik şu an preview olarak geliyor ama heyecan verici. Artık ref struct'lar interface implement edebiliyor ve generic parametrelere "allows ref struct" kısıtıyla geçebiliyor:
public interface ISayici
{
void Artir();
int Deger { get; }
}
public ref struct HizliSayici : ISayici
{
private int _deger;
public void Artir() => _deger++;
public int Deger => _deger;
}
public void KullanSayici<T>(T sayici) where T : ISayici, allows ref struct
{
sayici.Artir();
Console.WriteLine(sayici.Deger);
}
Bu özellik ile yüksek performans gerektiren generic algoritmalarda Span<T> gibi ref struct'ları da kullanabiliyorsunuz.
8. Küçük Ama Değerli Dokunuşlar
- Overload resolution iyileştirmeleri: Derleyici artık daha iyi overload seçiyor, özellikle Span + array ikileminde.
- Implicit span conversions: T[] veya List<T> → Span<T> otomatik dönüştürme.
- Locking with LINQ: LINQ extension'ları bazı Span tipleri ile daha iyi çalışıyor.
- Performans: JIT derleyici ve runtime tarafında onlarca iyileştirme var. Tipik bir API uygulaması önceki versiyona göre %5-10 daha az bellek kullanıyor.
Gerçek Hayattan Bir Örnek: Önceki ve Sonraki
Şimdi tüm bu yenilikleri birleştiren küçük bir örnek yapalım. Kullanıcı verilerini thread-safe tutan ve loglayan bir sınıf:
using System.Threading;
public partial class KullaniciServisi
{
private readonly Lock _gate = new();
private readonly List<string> _kullanicilar = [];
public partial int ToplamKullanici { get; }
public partial string[] Sirali { get; }
public void Ekle(params ReadOnlySpan<string> isimler)
{
lock (_gate)
{
foreach (var isim in isimler)
{
if (!_kullanicilar.Contains(isim))
_kullanicilar.Add(isim);
}
Console.WriteLine($"\e[32m[OK]\e[0m {isimler.Length} kullanıcı eklendi.");
}
}
public string Son => _kullanicilar.Count > 0 ? _kullanicilar[^1] : "(boş)";
}
public partial class KullaniciServisi
{
public partial int ToplamKullanici => _kullanicilar.Count;
public partial string[] Sirali => [.. _kullanicilar.OrderBy(x => x)];
}
Kullanımı:
var servis = new KullaniciServisi();
servis.Ekle("Ali", "Veli", "Ayşe");
servis.Ekle("Mehmet", "Fatma");
Console.WriteLine($"Toplam: {servis.ToplamKullanici}");
Console.WriteLine($"Sıralı: {string.Join(", ", servis.Sirali)}");
Console.WriteLine($"Son eklenen: {servis.Son}");
Bu kod parçasında C# 13'ün en az 5 ayrı yeniliğini kullandık: partial property, params ReadOnlySpan, yeni Lock tipi, collection expression ([], [.. _]), ve escape sequence \e.
Sonuç
C# 13 dile küçük ama çok yerinde dokunuşlar yapıyor. Özellikle yeni Lock tipi ve params collections performans-kritik uygulamalarda hissedilir fark yaratıyor. Partial property ise source generator ekosisteminin daha da zenginleşmesinin yolunu açıyor.
Bence upgrade etmek için çok iyi bir zaman. .NET 9 LTS değil ama pek çok projede sorunsuz çalışıyor ve bir sonraki LTS olan .NET 10'a geçişi kolaylaştırıyor. Herkese kolay gelsin, yorumlarda tecrübelerinizi paylaşmayı unutmayın. Başka bir makalede görüşmek üzere...