Span karşılaştırmaları eski yöntem ve yeni yöntem
1. Eski Yöntem (Hafızayı Yoran Yol)
2. Yeni Yöntem: ReadOnlySpan (Işık Hızında Yol)
Span ile hiçbir şeyi kopyalamıyoruz. Sadece ana metnin üzerine bir "şeffaf cetvel" koyuyoruz.
C# 14 ile API Veri İşleme Örneği Aşağıdaki kodda, gelen ham veriyi (network stream gibi) Span ile nasıl parçalara ayırdığımızı görebilirsin.
C# 14'te Neler Değişti? Senin için bu kodun neden "yeni nesil" olduğunu maddelerle açıklayayım: u8 Takısı: Eskiden metinleri byte dizisine çevirmek için Encoding.UTF8.GetBytes() derdik, bu da yeni bir kopya oluştururdu. C# 14 ile u8 yazdığında, derleyici bunu en baştan "akıllı veri" (static data) olarak belleğe koyar.
ReadOnlySpan: Bu bizim "zırhlı penceremizdir". Hem çok hızlıdır hem de kimsenin bu veriyi değiştirmesine izin vermez.
Sıfır Tahsis (Zero Allocation): Bu kod çalıştığında, Garbage Collector (Çöpçü) gelip hiçbir şeyi temizlemek zorunda kalmaz. Çünkü arkada hiç çöp (geçici kopya) bırakmadık.
Şimdi veritabanında veri çekerken yapılması gereken işlemlere bakalım
Küçük bir uyarı:
Büyük bir json verisini okurken aşağıdaki gibi bir akış gerekleştirebiliriz
string gelenVeri = "2026-03-29|ERROR|Sunucu_Coktu_Imdat!";
// Substring her seferinde RAM'de YENİ BİR STRING oluşturur.
// 1 milyon istek gelirse, 1 milyon tane "ERROR" kelimesi RAM'i doldurur.
string durum = gelenVeri.Substring(11, 5);
if (durum == "ERROR")
{
Console.WriteLine("Alarm çalıyor!");
}
2. Yeni Yöntem: ReadOnlySpan
Span ile hiçbir şeyi kopyalamıyoruz. Sadece ana metnin üzerine bir "şeffaf cetvel" koyuyoruz.
string gelenVeri = "2026-03-29|ERROR|Sunucu_Coktu_Imdat!";
// AsSpan() dediğimiz an, veriyi kopyalamıyoruz.
// Sadece "11. harften başla, 5 harf ilerle" diyoruz.
ReadOnlySpan durumPenceresi = gelenVeri.AsSpan(11, 5);
// "ERROR" ile karşılaştırırken bile yeni bir kopya oluşmaz.
if (durumPenceresi.SequenceEqual("ERROR"))
{
Console.WriteLine("Alarm çalıyor (ve RAM tertemiz!)");
}
C# 14 ile API Veri İşleme Örneği Aşağıdaki kodda, gelen ham veriyi (network stream gibi) Span
using System;
// 1. Verinin Orijinal Hali (Bellekte bir kez durur)
// "u8" eki, bunun doğrudan byte (UTF-8) olduğunu söyler.
ReadOnlySpan gelenPaket = "ID:123|TUR:KRITIK|MESAJ:SunucuIsindi"u8;
// 2. Garsonun Taşıma Hızı (Kopyalama YOK)
// Sadece 7. karakterden başlayıp 6 karakterlik bir "pencere" açıyoruz.
// Bu işlem 0 (sıfır) nano-saniye sürer!
ReadOnlySpan turBolumu = gelenPaket.Slice(7, 6);
// 3. Güvenlik Duvarı (Sınır Kontrolü)
// Eğer Slice(7, 50) deseydik, C# bizi anında durdururdu.
// Dışarıdan gelen virüslü devasa veriler sınırımızı aşamaz.
// 4. API İçindeki Trafiği Azaltmak (String'e çevirmeden kontrol)
// Veriyi "string"e dönüştürüp RAM'i kirletmeden doğrudan byte seviyesinde bakıyoruz.
if (turBolumu.SequenceEqual("KRITIK"u8))
{
Console.WriteLine("DİKKAT: API kritik bir durum tespit etti!");
}
// 5. Mesaj kısmına "bakmak"
ReadOnlySpan mesajBolumu = gelenPaket.Slice(20);
Console.WriteLine($"Mesaj uzunluğu: {mesajBolumu.Length} byte.");
C# 14'te Neler Değişti? Senin için bu kodun neden "yeni nesil" olduğunu maddelerle açıklayayım:
| Durum | Ne Kullanmalısın? |
| Veri Çekerken | IAsyncEnumerable<T> (Hafıza şişmez) |
| Veriyi İşlerken | Span<T> ve ref (İşlemci yorulmaz) |
| Sonuç Dönerken | Eğer sadece okunacaksa IEnumerable<T>, liste şartsa List<T> |
Span<T> sadece "kısa süreli" (stack) işlerde kullanılır. Veritabanından gelen veriyi bir Span içinde tutup, 5 dakika sonra başka bir fonksiyona gönderemezsin. O anlık işlem yapıp bitirmen gerekir.
public async Task> MaaslariGuncelleAsync()
{
// 1. Veriyi çekiyoruz (Bu bir List oluşturur)
List kullanicilar = await _db.Kullanicilar.ToListAsync();
// 2. İşlem için Span "penceresini" açıyoruz
// Bu sadece bu metodun içinde geçerli bir 'hızlı erişim' yoludur.
Span kullaniciSpan = CollectionsMarshal.AsSpan(kullanicilar);
// 3. Yerinde (in-place) güncelleme yapıyoruz
foreach (ref var kullanici in kullaniciSpan)
{
kullanici.Maas *= 1.10m;
}
// 4. Listeyi geri döndürüyoruz.
// Liste zaten güncellendi, çünkü Span doğrudan listenin içine baktı!
return kullanicilar;
}
Senin verdiğin örnekte ToListAsync() kullandığın için tüm veriyi zaten RAM'e indirdin. IAsyncEnumerable ise veriyi parça parça işlemek içindir.
Eğer 1 milyon kullanıcı varsa, hepsini bir listeye koyup sonra Span ile dönmek yerine şöyle yaparsın:
public async IAsyncEnumerable MaaslariAkislaGuncelleAsync()
{
// Veritabanından veriler tek tek (veya küçük paketlerle) gelir
await foreach (var kullanici in _db.Kullanicilar.AsAsyncEnumerable())
{
kullanici.Maas *= 1.10m; // Her gelen kullanıcıyı anında güncelle
yield return kullanici; // Hemen dışarıya gönder (bekleme yapma)
}
}
Yukarıdaki IAsyncEnumerable artık tolist() yapılmamalıdır
// KÖTÜ ÖRNEK: Avantajı öldürür
var liste = await MaaslariAkislaGuncelleAsync().ToListAsync(); // YAPMA!
return Ok(liste);
// İYİ ÖRNEK: Akışı bozmadan devam ettir
[HttpGet]
public async IAsyncEnumerable GetMaaslar()
{
// Veriler veritabanından geldikçe, API'den JSON olarak tek tek çıkmaya başlar
await foreach (var kullanici in MaaslariAkislaGuncelleAsync())
{
// Burada ek bir işlem yapabilirsin (loglama vs.)
yield return kullanici;
}
}
bu gelen datayı frontend üzerine beklemeden göstermek için aşağıdaki javascript kodu yazılabilir
const response = await fetch('/api/kullanicilar');
const reader = response.body.getReader(); // Musluğu açtık
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read(); // Yeni bir parça geldi mi?
if (done) break; // Veri bitti
const chunk = decoder.decode(value); // Gelen "parçayı" metne çevir
console.log("Yeni bir kullanıcı geldi:", chunk);
// Ekrana (listeye) bu kullanıcıyı hemen ekle!
renderUserToUI(JSON.parse(chunk));
}
| Senaryo | Frontend Ne Yapar? | Kullanıcı Ne Görür? |
| Büyük Veri (Rapor) | Veri geldikçe tabloya satır ekler. | Sayfa donmaz, verilerin "aktığını" görür. |
| Küçük Veri | Tarayıcı veriyi arkada biriktirir, bitince gösterir. | List<T> ile aynı gibi görünür ama sunucu daha az yorulur. |
| Mobil Uygulama | Veri akarken "50/1000 kayıt yüklendi" gibi canlı sayaç gösterir. | Uygulamanın çok hızlı çalıştığını hisseder. |
using System.Text.Json;
public async Task UcakVerileriniIsleHizli()
{
using var client = new HttpClient();
// 1. ÖNEMLİ: Veriyi string olarak değil, STREAM (akış) olarak alıyoruz.
// Bu sayede 100MB veriyi RAM'e bir kerede yüklemiyoruz.
using Stream akis = await client.GetStreamAsync("https://api.havayolu.com/tum-ucaklar");
// 2. JSON'ı parça parça okuyan modern ayarlar
var options = new JsonReaderOptions { AllowTrailingCommas = true };
// Buffer: İnternetten gelen verileri koyacağımız geçici küçük bir kap (64KB gibi)
byte[] buffer = new byte[64 * 1024];
int okunanByte;
while ((okunanByte = await akis.ReadAsync(buffer)) > 0)
{
// 3. İŞTE SPAN BURADA!
// Buffer'ın içindeki dolu kısma bir "pencere" (Span) açıyoruz.
ReadOnlySpan veriDilimi = buffer.AsSpan(0, okunanByte);
// 4. Utf8JsonReader ile string'e çevirmeden doğrudan byte üzerinden parse et
var reader = new Utf8JsonReader(veriDilimi, options);
while (reader.Read())
{
// Örn: Sadece "UcakID" özelliğini yakala
if (reader.TokenType == JsonTokenType.PropertyName && reader.ValueTextEquals("UcakID"u8))
{
reader.Read(); // Değere geç
// Uçak ID'sini kopyalamadan Span olarak oku ve işle
Console.WriteLine($"Uçak Bulundu: {reader.GetString()}");
}
}
}
}
hybrid modelle ilgili datayı rame alabiliz
using System.Text.Json;
public async Task UcakVerileriniIsleHizli()
{
using var client = new HttpClient();
// 1. ÖNEMLİ: Veriyi string olarak değil, STREAM (akış) olarak alıyoruz.
// Bu sayede 100MB veriyi RAM'e bir kerede yüklemiyoruz.
using Stream akis = await client.GetStreamAsync("https://api.havayolu.com/tum-ucaklar");
// 2. JSON'ı parça parça okuyan modern ayarlar
var options = new JsonReaderOptions { AllowTrailingCommas = true };
// Buffer: İnternetten gelen verileri koyacağımız geçici küçük bir kap (64KB gibi)
byte[] buffer = new byte[64 * 1024];
int okunanByte;
while ((okunanByte = await akis.ReadAsync(buffer)) > 0)
{
// 3. İŞTE SPAN BURADA!
// Buffer'ın içindeki dolu kısma bir "pencere" (Span) açıyoruz.
ReadOnlySpan veriDilimi = buffer.AsSpan(0, okunanByte);
// 4. Utf8JsonReader ile string'e çevirmeden doğrudan byte üzerinden parse et
var reader = new Utf8JsonReader(veriDilimi, options);
while (reader.Read())
{
reader.Read();
if (reader.GetString() == "THY123") // Aradığımız uçağı bulduk!
{
// 2. ŞİMDİ SİHİR BURADA:
// Tüm uçağı veya içindeki nested yapıyı (Rota gibi)
// olduğu yerden (Span/Buffer) küçük bir modele çeviriyoruz.
// Bu satır sadece o küçük kısmı modele dönüştürür, 100MB'ı değil!
UcakDetay ucak = JsonSerializer.Deserialize(ref reader);
return ucak; // Sadece tek bir küçük objeyi RAM'e aldık.
}
}
}
}
| Eğer Amacın Şeysyse... | Önerilen Yol | Neden? |
| "Tüm uçakları ekranda listeleyeceğim" | IAsyncEnumerable<Ucak> | Veriyi akıtarak (stream) gönderirsin, RAM şişmez. |
| "Sadece belirli bir uçağın detayını bulup döneceğim" | Span ile tara + Deserialize | 100MB'ı nesneye çevirmekle uğraşmaz, hedefi vurup çıkarsın. |
| "Uçakları alıp yüksekliklerine göre sıralayıp öyle döneceğim" | ToListAsync() | Sıralama (Sort) için tüm verinin elimizde olması şarttır. |
Eğer o nested yapıyı (iç içe geçmiş JSON) bir metot dışına çıkarman gerekiyorsa, onu bir class veya record yapısına Deserialize etmelisin. Çünkü Span, metot bittiği an ölür. Ama bu deserialization işlemini tüm dosya için değil, sadece bulduğun o küçük parça için yapmalısın.
Böylece:
Span sayesinde devasa veriyi ışık hızında tararsın (Arama hızı).
JsonSerializer.Deserialize(ref reader) ile sadece ihtiyacın olan kısmı RAM'e çıkarırsın (Hafıza tasarrufu).
Yorumlar
Yorum Gönder