Kaynak (İngilizce orijinal): Improving C# Memory Safety — .NET Blog · Richard Lander · 21 Mayıs 2026
Microsoft, C#'ta bellek güvenliğini yeniden tanımlıyor. unsafe artık sadece "pointer yazabilirsin" demek değil; çağıran tarafa yüklenen bir sözleşme anlamına geliyor. Derleyici, bellek kurallarını bozabilecek her işlemi görünür kılıyor. AI ile üretilen kod arttıkça bu görünürlük code review için kritik hale geliyor.
Yeni model (C# 16) .NET 11'de preview, .NET 12'de production hedefleniyor. Başlangıçta isteğe bağlı (opt-in); nullable reference types gibi kademeli geçiş planlanıyor.
Güvenli C# zaten ne yapıyor?
Normal C# kodunda runtime ve derleyici birlikte çalışır:
- Referanslar ya geçerli bir nesneyi, ya
null'u, ya da artık geçersiz olanı gösterir. - Yeni nesneler varsayılan olarak sıfırlanır.
- Dizi erişiminde sınır kontrolü yapılır; geçersiz indeks
IndexOutOfRangeExceptionfırlatır — yani off-by-one hatasında rastgele bellek okumak yerine kontrollü bir hata alırsınız.
Güvenli kod canlı bellek kuralını yapısal olarak korur: her erişim ayrılmış, başlatılmış ve kullanılabilir olmalıdır. unsafe kod — native kütüphane çağrısı veya performans için — bu kuralı geliştiriciye bırakır. Dilin rolü unsafe kod yazmanıza yardım etmek değil; sınırların nerede olduğunu ve güvenli koda nasıl dönüldüğünü netleştirmektir.
Karayolu benzetmesi
Sarı çizgi sürücü disiplinine dayanır; bariyer ise yapısal ayrım sağlar. Programlamada da "hız" arttıkça (gigabaytlarca bellek erişimi) risk yükselir. Undefined Behavior (UB), yani tanımsız davranış — programın sessizce yanlış sonuç üretmesi veya çökmesi — endüstri güvenlik açıklarının ana kaynağıdır.
C# 16 modeli: dört katman
Yarım uygulama yarım değer verir. Dört katman birlikte çalışınca çağrı grafiği boyunca denetlenebilir bir güvenlik hattı oluşur:
- İç
unsafe { }bloğu: Pointer okuma, unsafe üye çağrısı gibi her riskli operasyon açıkça bu blok içinde olmalı. - Yayılım (propagation): Metot imzasına
unsafeeklerseniz, yükümlülük çağıranlara taşınır — tıpkı "bu metodu çağırmak için sen de unsafe bölgede olmalısın" demek gibi. /// <safety>dokümantasyonu: Metodun çağırandan ne beklediği yazılı olarak belgelenir; analyzer eksikliği işaretleyebilir.- Sınırda bastırma (suppression): İçeride
unsafevar ama imzaunsafedeğilse, metot güvenli bir API yüzeyi sunar — runtime kontrolü, statik akıl yürütme veya üst katmandaki invariant ile yükümlülük "boşaltılır".
C# 1.0'dan farklar
unsafeartık tipe değil, metot/özellik/field seviyesine iner.- Static constructor ve finalizer'da
unsafeyasak. new()constraint yalnızca güvenli parametresiz ctor kabul eder.- Yeni
safekeyword:extern/LibraryImportiçin açık seçim zorunlu. unsafeimza artık unsafe context kurmaz; çağrı noktasında iç blok şart.- Pointer imzada tek başına unsafety yaymaz; dereference unsafe'dir. Yeni kodda
IntPtryerinebyte*tercih edin; opak handle içinSafeHandle.
Örnek: iyi ve kötü unsafe API
Encoding.GetString(byte*, int) iyi örnek: pointer + uzunluk çifti net sözleşme; null/negatif guard'lar; dönüş yeni string — buffer yaşam süresi sorunu kalmaz. Çağıranın tek yükümlülüğü: byteCount kadar byte okunabilir olmalı.
Marshal.ReadByte(IntPtr, int) uyarıcı örnek: bugün güvenli koddan çağrılabiliyor; IntPtr yayılım kuralını atlıyor — "pointer gizlice taşıma". try/catch yalnızca exception tipini değiştirir; gerçek doğrulama yok.
Yeni modelde ReadByte imzası unsafe kalır; okuma tek satırda iç unsafe bloğuna alınır; /// <safety> okuma iznini yazar. İhlaller uyarı değil, derleme hatası olur.
Yayılım ve bastırma
C# 1.0'da unsafe void M() { } tüm metodu unsafe context yapıyordu; pointer imzada yoksa çağıran töreni görmezdi — convention'a dayalı, denetlenemez bir sistem.
C# 16'da iki net yol var:
- Yayılım: Dış
unsafe+ içunsafe { M(); }— yükümlülük yukarı taşınır. - Bastırma: Guard ile yükümlülük boşaltılır, imza güvenli kalır — örneğin null kontrolü yapıp sonra unsafe işlemi içeride yapmak.
Cross-assembly davranış proje opt-in'ine bağlı: yeni model açık çağıran + yeni callee → yeni kurallar; legacy callee → uyumluluk modu; legacy caller + yeni callee → yeni marker'lar zorlanmaz. Runtime kütüphaneleri opt-in olacak. Derleme zamanı only; runtime performans etkisi yok.
Proje seviyesi opt-in
İki bağımsız anahtar:
- Yeni güvenlik modeli property (.NET 11 preview ile isim netleşecek) — kapalıyken C# 1.0 kuralları.
- Mevcut
AllowUnsafeBlocks— varsayılanfalse; tümunsafegörünümünü kapılar.
En güvenli kombinasyon: yeni model açık, AllowUnsafeBlocks kapalı → Marshal.ReadByte derleme aşamasında engellenir.
dotnet format fixer çağrı noktalarına unsafe { } sarmayı otomatik yapabilir; /// <safety> yazamaz. AllowUnsafeBlocks=false ile derleyici unsafe kodu reddeder — diff review'dan daha verimli.
Safety documentation
"Unsafe" kelimesi "güvenlikleri kapat" demek; derleyici doğrulayamaz, yükümlülük geliştiricide kalır.
/// <safety>: Caller contract — public API yüzeyinde, çağırandan ne beklendiğini yazar.// SAFETY:: İç not — hangi varsayıma dayanıldığı; derleyici okumaz.
ReadByte için IntPtr durumları: Zero → NRE; unmapped → donanım hatası; yanlış sahiplik → sessizce rastgele byte (klasik UB); doğru pointer → çalışır. Sözleşme görünür olmadan çağıran koruyamaz.
Safety guards
Dokümantasyon yükümlülükleri adlandırır; guard'lar boşaltır. String.CopyTo örneği: her ThrowIf* bir invariant korur — ThrowIfNull, negatif count, indeks sınırı. Guard'lar birleşir; bir link değişirse akıl yürütme yeniden yapılmalı.
ThrowIf* = UB yerine kontrollü crash. Unsafe boundary method: imza güvenli, içeride unsafety bastırılmış — code review buradan başlar.
Unsafe fields
Field unsafe olmalı when declared type invariant'ı taşımıyor:
NativeBuffer mockup: unsafe byte* _ptr + sabit Length; ctor'da NativeMemory.Alloc; ReadAt bounds + disposed check; Dispose free + null. Field marker tüm yazma noktalarını gözden geçirilebilir kılar.
ArrayWrapper<T>: readonly unsafe Array _array — runtime her zaman T[] tutar; Unsafe.As<T[]> ile okuma. Motivasyon aynı: invariant tip sisteminde görünmüyor.
Kurallar: yazma birincil motivasyon; readonly unsafe = sözleşme + built-in guard; private serbest geçiş değil — member-to-member de sözleşme yüzeyi.
Migration walkthrough (özet)
Runtime kütüphanelerinde pattern: unsafe API → caller → inline discharge mi propagation mı?
NativeMemory.Alloc → safe (pointer tutmak unsafe değil; OOM throw eder). NativeMemory.Free → unsafe + <safety>: Alloc'tan gelmiş, henüz free edilmemiş, alias yok.
FileVersionInfo constructor mockup: scratch buffer allocation inner unsafe'da; parse metodu propagation veya boundary kararı. Her caller potansiyel boundary.
(Model henüz finalize değil; örnekler yön gösterici.)
AI çağında neden önemli?
Bellek güvenliği endüstri ve kamu önceliği; AI kod üretimi review kapasitesini aşıyor. unsafe artık "uzman convention" değil — grep ile audit edilebilir, derleyici zorunlu sözleşme. Backend ekibi olarak interop path'lerinde PR checklist: /// <safety>, inner block, AllowUnsafeBlocks property, propagation vs suppression kararı.
Özet
C# 16, unsafe kodu görünür ve denetlenebilir kılar: iç blok, imza yayılımı, <safety> dokümantasyonu ve sınırda bastırma birlikte çalışır. Tam kod örnekleri ve migration PR listesi orijinal yazıda.
