# C# Programlama

> C# 14 ve .NET 10 ekseninde CLR, tür sistemi, nesne yönelimli tasarım, genel türler, LINQ, async/await, iş parçacıkları, atomiklik, kanallar, asenkron akışlar, bellek yönetimi, Pipelines, G/Ç, ağ, yansıma ve üretim performansını örneklerle ele alan kapsamlı ders notu.

- Author: Muhammet Ali Köker
- Language: tr
- Canonical: https://alikoker.com.tr/csharp-programlama
- Translation: https://alikoker.com.tr/en/csharp-programming
- Published: 2015-07-01T18:34:11+03:00
- Modified: 2026-09-16T02:53:58+03:00
- Verified: 2026-09-16T02:53:58+03:00
- Type: article

C#; statik tür güvenliği, nesne yönelimli model, fonksiyonel ifade araçları, asenkron programlama ve çalışma zamanı hizmetlerini aynı dilde birleştirir. Dili doğru öğrenmek için üç katmanı ayırmak gerekir: C# kaynak dilidir, derleyici kaynak kodu ara dile ve üst veriye dönüştürür, .NET çalışma zamanı ise assembly'leri yükler ve kodu yürütür.

C# sözdizimi, dil semantiği ve çalışma zamanı maliyetleri birlikte değerlendirilmelidir. Temel dil yapıları ilerleyen bölümlerde genel türler, LINQ, eşzamanlılık, bellek, ağ ve reflection gibi konularla aynı model içinde birleşir.

## 1. C#, .NET ve CLR

Modern .NET, yalnız Windows'a bağlı eski .NET Framework modeli değildir. .NET 10 uzun süreli destek sürümüdür ve C# 14 ile birlikte kullanılabilir. CLR; assembly yükleme, JIT derleme, çöp toplama, istisna aktarımı, iş parçacığı yönetimi ve tür güvenliği gibi çalışma zamanı hizmetlerini sağlar.

C# kaynak kodu doğrudan işlemci komutlarından oluşmaz. Derleme sonucunda assembly içinde Common Intermediate Language ve üst veri bulunur. Çalışma zamanı ihtiyaç duyulan metotları yerel makine koduna dönüştürebilir. Native AOT ise uygun uygulamalarda yürütülebilir kodu önceden üretme seçeneği sunar.

```csharp
using System;

public static class Program
{
    public static void Main()
    {
        Console.WriteLine($".NET: {Environment.Version}");
        Console.WriteLine($"64 bit süreç: {Environment.Is64BitProcess}");
    }
}
```

Yönetilen kod bellek güvenliği ve çalışma zamanı hizmetlerinden yararlanır. Bununla birlikte dosya tanıtıcısı, socket, veritabanı bağlantısı veya yerel tanıtıcı gibi dış kaynakların ömrünü çöp toplayıcıya bırakmak doğru değildir.

## 2. Derleme, SDK ve proje modeli

Güncel komut satırı akışı `dotnet` CLI üzerine kuruludur:

```text
dotnet new console -n Ornek
dotnet build Ornek
dotnet run --project Ornek
```

SDK hedef framework, dil sürümü, nullable analiz ve optimizasyon gibi seçenekleri proje dosyasından yönetir. Üretimde kullanılan dil özelliği, yalnız geliştiricinin IDE sürümüne değil teslim hattındaki SDK ve derleyici sürümüne de bağlıdır.

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>
```

## 3. Tür sistemi: değer ve referans semantiği

C# tür sistemi, yalnız değişkenin hangi değeri tutacağını değil kopyalama, null olabilme, boxing ve bellek davranışını da belirler. `int`, `double`, `bool`, `char`, `decimal` ve kullanıcı tanımlı `struct` türleri değer türleridir. Sınıflar, diziler, delegate'ler ve `string` referans türüdür.

`decimal`, ondalık finansal hesaplarda ikili kayan noktanın temsil hatalarını azaltmak için tasarlanmıştır. `double` ise bilimsel ve genel sayısal hesaplamalarda daha yaygın ve hızlıdır.

```csharp
decimal net = 1250.75m;
decimal taxRate = 0.20m;
decimal total = net * (1m + taxRate);

int count = 42;
long widened = count;

checked
{
    int narrowed = checked((int)3_000_000_000L);
    Console.WriteLine(narrowed);
}
```

Son örnek `OverflowException` üretir. `checked` taşmanın sessizce sarmalanmasının kabul edilmediği hesaplarda önemlidir.

## 4. Nullable türler ve null güvenliği

Nullable reference types çalışma zamanı özelliği değil, derleyici tarafından yapılan statik analizdir. `string` null olmaması beklenen, `string?` ise null olabilen referansı ifade eder. Bu ayrım API sözleşmesini görünür kılar.

```csharp
static int LengthOrZero(string? value)
{
    return value?.Length ?? 0;
}

string? input = Console.ReadLine();
Console.WriteLine(LengthOrZero(input));
```

Null-forgiving `!` operatörü null kontrolü yapmaz; yalnız derleyici uyarısını bastırır. Bu nedenle gerçek bir invariant tarafından desteklenmediği sürece kullanılmamalıdır.

## 5. Operatörler, kısa devre ve bit işlemleri

`&&` ve `||` kısa devrelidir; sağ taraf yalnız gerektiğinde değerlendirilir. `&`, `|`, `^`, `~`, `<<` ve `>>` bit düzeyinde protokol alanı, maske ve düşük seviye veri işleme gibi durumlarda kullanılır.

```csharp
const byte Valid = 0b0000_0001;
const byte Encrypted = 0b0000_0010;

byte flags = Valid | Encrypted;

bool valid = (flags & Valid) != 0;
bool encrypted = (flags & Encrypted) != 0;

Console.WriteLine($"{valid} {encrypted}");
```

Bit alanları için anlamlı sabitler veya `[Flags]` enum kullanmak çıplak sayılardan daha güvenlidir.

## 6. Karar yapıları ve örüntü eşleme

Klasik `if` ve `switch` hâlâ temel yapılardır; modern C# bunları örüntü eşleme ile genişletir. Tür, sabit, ilişki ve property pattern'leri karar mantığını daha bildirisel yazmayı sağlar.

```csharp
static string Classify(int latencyMs) => latencyMs switch
{
    < 0 => throw new ArgumentOutOfRangeException(nameof(latencyMs)),
    < 20 => "çok düşük",
    < 100 => "düşük",
    < 500 => "orta",
    _ => "yüksek"
};

Console.WriteLine(Classify(87));
```

Örüntü eşleme, uzun `if-else` zincirini kısaltabilir; ancak iş kuralı büyüdükçe karar tablosu veya ayrı politika nesnesi daha sürdürülebilir olabilir.

## 7. Döngüler ve enumerator modeli

`for`, sayaç veya indeks gerektiğinde; `foreach`, koleksiyonun elemanları üzerinde ilerlerken; `while` koşul başta, `do-while` koşul sonda değerlendirildiğinde uygundur.

```csharp
int[] samples = [12, 18, 21, 17, 25];

long sum = 0;
foreach (int sample in samples)
{
    sum += sample;
}

double average = (double)sum / samples.Length;
Console.WriteLine(average);
```

`foreach` sözdizimi `IEnumerable<T>` ile sınırlı değildir; uygun `GetEnumerator` pattern'ini sağlayan türler de kullanılabilir.

## 8. Metotlar, parametre aktarımı ve `ref` ailesi

C# varsayılan olarak argümanı değer olarak aktarır. Referans türünde bu değer nesnenin kendisi değil referansın kopyasıdır. `ref`, `out` ve `in` parametre aktarım semantiğini değiştirir.

```csharp
static bool TryNormalize(int value, out double normalized)
{
    if (value is < 0 or > 100)
    {
        normalized = 0;
        return false;
    }

    normalized = value / 100.0;
    return true;
}

if (TryNormalize(73, out double result))
{
    Console.WriteLine(result);
}
```

Çok sayıda değer döndürmek için `out` yerine tuple veya küçük bir record/struct çoğu zaman daha okunabilirdir. `ref` özellikle kopyalama maliyeti yüksek değer türlerinde ve düşük seviyeli API'lerde anlamlıdır.

## 9. Sınıflar, kapsülleme ve nesne ömrü

Alanları doğrudan herkese açmak yerine invariant'ı koruyan property ve metotlar tercih edilir. Kurucu, nesnenin geçerli başlangıç durumunu kurmalıdır.

```csharp
public sealed class RateLimiter
{
    private int _limit;

    public RateLimiter(int limit)
    {
        Limit = limit;
    }

    public int Limit
    {
        get => _limit;
        set => _limit = value > 0
            ? value
            : throw new ArgumentOutOfRangeException(nameof(value));
    }
}
```

C# 14 `field` anahtar sözcüğüyle compiler-generated backing field üzerinde doğrulama yapılmasını kolaylaştırır:

```csharp
public sealed class Endpoint
{
    public string Name
    {
        get;
        set => field = string.IsNullOrWhiteSpace(value)
            ? throw new ArgumentException("Ad boş olamaz.", nameof(value))
            : value.Trim();
    } = "default";
}
```

Bu ikinci örnek C# 14 gerektirir.

## 10. Record, eşitlik ve değişmezlik

Referans eşitliği ile değer eşitliği farklıdır. Normal sınıflarda `==` varsayılan olarak çoğunlukla referans kimliğiyle ilgilidir; record türleri ise veri taşıyan modeller için değer-temelli equality üretir.

```csharp
public sealed record SensorReading(string Source, long Sequence, double Value);

SensorReading a = new("A1", 7, 42.5);
SensorReading b = new("A1", 7, 42.5);

Console.WriteLine(a == b);

SensorReading c = a with { Sequence = 8 };
Console.WriteLine(c);
```

Kendi sınıfınızda equality tanımlıyorsanız `Equals` ve `GetHashCode` sözleşmeleri birlikte düşünülmelidir. Hash tabanlı koleksiyonlarda değişebilir alanlardan hash üretmek ciddi hatalara yol açabilir.

## 11. Kalıtım, bileşim ve çok biçimlilik

Kalıtım bir "is-a" ilişkisini temsil etmelidir. Yalnız kod tekrarını azaltmak için hiyerarşi kurmak kırılgan tasarıma neden olabilir. Davranış değişimini bileşim ve interface ile modellemek çoğu durumda daha esnektir.

```csharp
public interface IFormatter
{
    string Format(double value);
}

public sealed class FixedFormatter : IFormatter
{
    public string Format(double value) => value.ToString("F2");
}

public sealed class SampleWriter
{
    private readonly IFormatter _formatter;

    public SampleWriter(IFormatter formatter)
    {
        _formatter = formatter;
    }

    public string Write(double value) => _formatter.Format(value);
}
```

`virtual`/`override` dinamik dispatch sağlar. `sealed` hem tipin hem override zincirinin sınırlandırılmasında kullanılabilir.

## 12. Arayüz, struct, enum ve kutulama

`struct` küçük, değer semantiği taşıyan ve mümkünse değişmez veri modelleri için uygundur. Büyük mutable struct'lar kopyalama maliyeti ve şaşırtıcı semantik üretir. `readonly record struct` bu tür modeller için güçlü bir seçenektir.

```csharp
public enum PacketState : byte
{
    Unknown,
    Ready,
    Sent,
    Failed
}

public readonly record struct Coordinate(double X, double Y);

Coordinate p = new(12.5, 8.25);
PacketState state = PacketState.Ready;
Console.WriteLine($"{p} {state}");
```

Bir değer türü `object` veya uyguladığı interface türüne dönüştürüldüğünde boxing oluşabilir. Sık çalışan sıcak yollarda gereksiz kutulama kaynaklı ayırma ve GC baskısı yaratabilir.

## 13. Diziler, `Span<T>` ve bellek görünümü

Diziler sabit uzunluklu referans türleridir. Çok boyutlu gerçek diziler `T[,]`, jagged diziler ise dizi dizisi `T[][]` biçimindedir. Performans duyarlı kodlarda `Span<T>` ve `ReadOnlySpan<T>` mevcut belleğin yeni bellek ayırma yapmadan dilimlenmesini sağlar.

```csharp
static int SumPositive(ReadOnlySpan<int> values)
{
    int sum = 0;

    foreach (int value in values)
    {
        if (value > 0)
        {
            sum += value;
        }
    }

    return sum;
}

int[] values = [4, -2, 7, 3];
Console.WriteLine(SumPositive(values.AsSpan(1, 3)));
```

`Span<T>` bir `ref struct` olduğundan heap'e taşınamaz ve yaşam süresi kuralları daha katıdır. Bu kısıtlar güvenli düşük-bellek ayırma kod üretmek içindir.

## 14. Dize, biçimlendirme ve kültür

`string` değişmez'dır. Çok sayıda parçayı döngü içinde birleştirmek için `StringBuilder`, formatlama için interpolation veya uygun format API'leri kullanılabilir.

```csharp
using System.Globalization;

double value = 12345.6789;

Console.WriteLine(value.ToString("F2", CultureInfo.InvariantCulture));
Console.WriteLine($"{value:N2}");
```

Makine-makine protokollerinde kültürden bağımsız biçim (`InvariantCulture`) tercih edilmelidir. Kullanıcı arayüzünde ise kullanıcının kültürü korunmalıdır.

## 15. Genel türler ve koleksiyonlar

Eski non-generic `ArrayList` ve `Hashtable` yerine `List<T>`, `Dictionary<TKey,TValue>`, `HashSet<T>`, `Queue<T>` ve `Stack<T>` gibi generic koleksiyonlar tercih edilir. Böylece cast ihtiyacı ve kutulama (boxing) riski azalır.

```csharp
var latest = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase)
{
    ["A1"] = 12.4,
    ["B7"] = 19.8
};

latest["A1"] = 13.1;

if (latest.TryGetValue("a1", out double current))
{
    Console.WriteLine(current);
}
```

Koleksiyon seçimi erişim biçimine göre yapılmalıdır. `List<T>` indeks ve ardışık tarama, `Dictionary<TKey,TValue>` anahtar erişimi, `HashSet<T>` üyelik testi için uygundur.

## 16. LINQ: sorgu semantiği ve ertelenmiş yürütme

LINQ, koleksiyon işleme için ortak bir sorgu modeli sunar. `IEnumerable<T>` üzerindeki birçok operatör ertelenmiş yürütülür; sonuç enumerasyon başladığında üretilir.

```csharp
int[] samples = [4, 11, 7, 21, 9, 16];

var query = samples
    .Where(static x => x >= 10)
    .OrderBy(static x => x)
    .Select(static x => x * 2);

foreach (int item in query)
{
    Console.WriteLine(item);
}
```

`ToArray`, `ToList`, `Count` gibi terminal işlemler yürütmeyi tetikleyebilir. `IQueryable<T>` ise expression tree üzerinden sorguyu başka bir sisteme çevirebilir; bu nedenle bellek içi LINQ ile veritabanı LINQ'u aynı maliyet modeline sahip değildir.

## 17. Delegate, lambda ve olaylar

Delegate, imzalı çağrılabilir referanstır. Lambda, delegate veya expression tree üretmek için kısa sözdizimi sağlar. Event ise yayıncının abonelik kontrolünü koruduğu bildirim modelidir.

```csharp
public sealed class ThresholdMonitor
{
    public event EventHandler<double>? Exceeded;

    public void Observe(double value)
    {
        if (value > 80)
        {
            Exceeded?.Invoke(this, value);
        }
    }
}

var monitor = new ThresholdMonitor();
monitor.Exceeded += static (_, value) => Console.WriteLine($"Eşik aşıldı: {value}");
monitor.Observe(91.5);
```

Event aboneliklerinde yayıncı uzun ömürlü, abone kısa ömürlü ise abonelik kaldırılmadığında bellek tutulması oluşabilir.

## 18. İstisna yönetimi

İstisna, beklenmeyen veya normal dönüş değeriyle temsil edilmesi uygun olmayan hata durumları içindir. Akış kontrolü amacıyla sürekli istisna üretmek pahalıdır.

```csharp
static int ParsePort(string text)
{
    if (!int.TryParse(text, out int port) || port is < 1 or > 65535)
    {
        throw new ArgumentOutOfRangeException(nameof(text), "Geçerli bir TCP/UDP portu girilmelidir.");
    }

    return port;
}

try
{
    Console.WriteLine(ParsePort("70000"));
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine(ex.Message);
}
```

Exception yeniden fırlatılırken `throw;` mevcut stack trace'i korur. `throw ex;` ise tanılama bilgisini bozabilir. `finally` kaynak temizleme için kullanılabilir; fakat `using`/`await using` çoğu sahiplik senaryosunu daha açık ifade eder.

## 19. Kaynak sahipliği: `IDisposable` ve `IAsyncDisposable`

GC yönetilen belleği geri kazanır; dış kaynakların zamanında bırakılmasını garanti etmez. Deterministik temizlik için `IDisposable` kullanılır.

```csharp
using FileStream stream = File.OpenRead("data.bin");

byte[] buffer = new byte[4096];
int read = stream.Read(buffer, 0, buffer.Length);

Console.WriteLine(read);
```

Finalizer son çaredir; normal kaynak yönetiminin yerine geçmez. Native handle sahipliğinde `SafeHandle` tabanlı yaklaşım çoğu zaman doğrudan finalizer yazmaktan güvenlidir.

## 20. Dosya G/Ç ve modern asenkron API

`FileStream`, `StreamReader`, `StreamWriter`, `BinaryReader`, `BinaryWriter` hâlâ geçerlidir. Küçük ve basit işlemler için `File` sınıfı daha az kod gerektirir.

```csharp
static async Task<long> CountLinesAsync(string path, CancellationToken cancellationToken)
{
    long count = 0;

    await using FileStream stream = new(
        path,
        FileMode.Open,
        FileAccess.Read,
        FileShare.Read,
        bufferSize: 64 * 1024,
        options: FileOptions.Asynchronous | FileOptions.SequentialScan);

    using var reader = new StreamReader(stream);

    while (await reader.ReadLineAsync(cancellationToken) is not null)
    {
        count++;
    }

    return count;
}
```

Büyük dosyada tüm içeriği `ReadAllText` ile belleğe almak yerine akış bazlı işleme daha öngörülebilir bellek kullanımı sağlar.

## 21. `async`/`await`, `Task` ve işin sahipliği

`async` bir yöntemi arka plan iş parçacığına taşımaz. Yöntem, tamamlanmamış bir beklenebilir nesneye ulaşıncaya kadar çağıran bağlamda ilerler; `await` bekleme sırasında iş parçacığını bloke etmek yerine yöntemin devamını planlar. G/Ç ağırlıklı işlerde ölçeklenebilirlik kazancı buradan gelir.

CPU ağırlıklı hesaplama ile G/Ç beklemesi ayrılmalıdır. Ağ veya dosya API'si zaten gerçek bir asenkron yöntem sunuyorsa onu `Task.Run` içine sarmak ek bir iş parçacığı tüketir fakat G/Ç'yi daha asenkron yapmaz. `Task.Run`, masaüstü arayüzünün yanıt verebilirliğini korumak veya CPU ağırlıklı işi iş parçacığı havuzuna taşımak gibi durumlarda anlamlıdır.

```csharp
static async Task<string> DownloadWithDeadlineAsync(
    HttpClient client,
    Uri address,
    CancellationToken cancellationToken)
{
    Task<string> download = client.GetStringAsync(address, cancellationToken);

    return await download.WaitAsync(
        TimeSpan.FromSeconds(3),
        cancellationToken);
}
```

Bir `Task`, yalnız çalışma durumunu değil işin sahipliğini de temsil eder. Başlatılan görev ya `await` edilmeli, döndürülmeli, izlenen bir koleksiyonda tutulmalı veya sonuç/istisna davranışı açıkça sahiplenilmelidir. "Fire and forget" biçiminde kaybedilen görevler kapanış, hata ve kaynak ömrü bakımından belirsizlik yaratır.

`async void` normal uygulama mantığı için değil, dönüş türünü `void` olarak zorunlu kılan olay işleyicileri gibi sınırlı sözleşmeler için kullanılmalıdır.

## 22. Asenkronluk, eşzamanlılık ve paralellik

Bu üç kavram aynı değildir:

- **Asenkronluk**, beklerken çağıran iş parçacığını meşgul etmemeyi amaçlar.
- **Eşzamanlılık**, birden çok işin yaşam sürelerinin örtüşmesidir.
- **Paralellik**, birden çok işin gerçekten aynı anda farklı yürütme kaynaklarında ilerlemesidir.

Bir HTTP isteğini `await` etmek çoğu zaman paralel CPU çalışması üretmez. Buna karşılık `Parallel.For` çok çekirdekli CPU'da hesaplamayı paralelleştirebilir. Bir sunucu ise binlerce eşzamanlı isteği, her istek için ayrı bir iş parçacığı ayırmadan asenkron G/Ç ile taşıyabilir.

Mimari karar, "kaç thread var?" sorusundan önce işin doğasını belirlemelidir: CPU mu tüketiliyor, G/Ç mi bekleniyor, paylaşılan durum var mı, sıralama zorunlu mu ve eşzamanlılık sınırı nedir?

## 23. İş parçacığı modeli ve doğrudan `Thread` kullanımı

`Thread`, işletim sistemi tarafından zamanlanan bir yürütme yolunu temsil eder. Aynı süreçteki iş parçacıkları heap üzerindeki nesnelere ortak erişebilirken her iş parçacığının kendi çağrı yığını ve yürütme durumu vardır. Bu ortak adres alanı iletişimi ucuzlatır; yarış durumu ve görünürlük sorunlarını da mümkün kılar.

Modern .NET uygulamasında çoğu kısa veya orta ömürlü iş için doğrudan `Thread` oluşturmak yerine `Task`, iş parçacığı havuzu ve asenkron API'ler tercih edilir. Yine de uzun ömürlü, özel zamanlama/affinity gerektiren veya havuzun davranışından bilinçli biçimde ayrılması gereken sınırlı işlerde özel iş parçacığı gerekebilir.

```csharp
var worker = new Thread(() =>
{
    while (!stopping.IsCancellationRequested)
    {
        ProcessOneItem();
    }
})
{
    IsBackground = true,
    Name = "dedicated-worker"
};

worker.Start();
```

Bu örnek yalnız özel iş parçacığı gereksinimi varsa anlamlıdır. Genel amaçlı arka plan işi için `Task.Run` veya daha yüksek düzeyli bir kuyruk daha basit ve daha güvenlidir.

İş parçacığı önceliğini yükseltmek gerçek zaman garantisi sağlamaz. GC, işletim sistemi zamanlayıcısı, diğer süreçler ve donanım gecikmeleri hâlâ yürütme süresini etkiler.

## 24. İş parçacığı havuzu ve görev zamanlama

İş parçacığı oluşturmak yığın, çekirdek nesneleri ve zamanlayıcı maliyeti taşır. .NET iş parçacığı havuzu, çalışan iş parçacıklarını yeniden kullanır ve yük altında sayıyı dinamik biçimde ayarlar. `Task.Run`, varsayılan durumda CPU ağırlıklı kısa işi bu havuza kuyruğa almanın temel yollarından biridir.

Havuzun en önemli riski **bloklama kaynaklı açlık**tır. Çok sayıda havuz iş parçacığı `.Result`, `.Wait()`, uzun `lock`, eşzamanlı ağ çağrısı veya başka bloklayıcı API'lerde tutulursa yeni işler çalışacak iş parçacığı bekleyebilir. Bu nedenle doğal olarak asenkron olan yollar baştan sona asenkron tutulmalıdır.

```csharp
static async Task<int[]> LoadAllAsync(
    HttpClient client,
    IReadOnlyList<Uri> addresses,
    CancellationToken cancellationToken)
{
    Task<int>[] jobs = addresses
        .Select(async address =>
        {
            byte[] body = await client.GetByteArrayAsync(address, cancellationToken);
            return body.Length;
        })
        .ToArray();

    return await Task.WhenAll(jobs);
}
```

`Task.WhenAll` işleri tek tek ardışık beklemek yerine tamamlanmalarını birlikte izler. Bununla birlikte giriş kümesi çok büyükse sınırsız eşzamanlılık üretmemek gerekir; semafor veya sınırlı kanal kullanılmalıdır.

## 25. Paylaşılan durum, yarış durumu ve atomiklik

Bir ifadenin kaynak kodda tek satır olması onun atomik olduğu anlamına gelmez. `counter++`, okuma, artırma ve yazma adımlarına ayrılabilir. İki yürütücü bu adımları iç içe geçirirse güncelleme kaybolabilir.

```csharp
public sealed class UnsafeCounter
{
    private int _value;

    public void Increment() => _value++;
    public int Value => _value;
}
```

Yarış durumunun tehlikesi yalnız "yanlış sonuç" değildir; hata düşük olasılıklı ve zamana bağlı olduğundan testte görünmeyip üretimde ortaya çıkabilir.

İlk tercih paylaşılan değişebilir durumu azaltmaktır. Değişmez nesneler, mesajlaşma, sahipliği tek yürütücüde toplama ve sonuçları birleştirme, kilit ihtiyacını önemli ölçüde azaltabilir.

## 26. `Interlocked`, `Volatile` ve bellek görünürlüğü

Basit sayaç ve durum geçişlerinde tam bir kilit yerine `Interlocked` kullanılabilir. `Interlocked`, tek bir bellek konumu üzerinde atomik read-modify-write işlemleri sağlar.

```csharp
public sealed class AtomicCounter
{
    private long _value;

    public long Increment() => Interlocked.Increment(ref _value);
    public long Value => Interlocked.Read(ref _value);
}
```

Karşılaştır-ve-değiştir (`CompareExchange`) daha gelişmiş kilitsiz algoritmaların temel yapı taşlarından biridir:

```csharp
static double Max(ref double location, double candidate)
{
    while (true)
    {
        double snapshot = Volatile.Read(ref location);
        if (snapshot >= candidate)
            return snapshot;

        double observed = Interlocked.CompareExchange(
            ref location,
            candidate,
            snapshot);

        if (observed == snapshot)
            return candidate;
    }
}
```

`volatile`, belirli okuma/yazmaların görünürlüğü ve yeniden sıralanması üzerinde semantik sağlar; fakat `x++` gibi bileşik işlemleri atomik yapmaz. Birden fazla alan arasında invariant korunacaksa çoğu zaman kilit veya daha yüksek düzeyli veri yapısı daha doğrudur.

Bellek sıralaması düşük seviyeli bir konudur. Önce `lock`, `Interlocked`, `SemaphoreSlim`, kanal veya concurrent koleksiyon gibi sözleşmesi açık yapılar kullanılmalı; açık bellek bariyerleri ancak ölçülmüş ve doğrulanmış gereksinimde düşünülmelidir.

## 27. `lock`, `Monitor` ve `System.Threading.Lock`

Karşılıklı dışlama, bir kritik bölgeyi aynı anda tek yürütücünün değiştirmesini sağlar. .NET 9 ve C# 13'ten itibaren genel amaçlı kilitlemede ayrılmış bir `System.Threading.Lock` örneği kullanılabilir; C# 14/.NET 10 kodunda bu yaklaşım doğrudan uygundur.

```csharp
public sealed class Balance
{
    private readonly System.Threading.Lock _gate = new();
    private decimal _value;

    public void Add(decimal amount)
    {
        lock (_gate)
        {
            _value += amount;
        }
    }

    public decimal Read()
    {
        lock (_gate)
        {
            return _value;
        }
    }
}
```

Kilit nesnesi dışarı sızdırılmamalıdır. `this`, `typeof(...)` veya intern edilebilen dize sabitleri üzerinde kilitleme, dış kodun aynı kilide istemeden ortak olmasına neden olabilir.

`lock` gövdesi içinde `await` kullanılamaz. Bunun nedeni kritik bölümün iş parçacığına bağlı karşılıklı dışlama semantiğidir. Asenkron bekleme içeren bir karşılıklı dışlama gerekiyorsa `SemaphoreSlim.WaitAsync` gibi bir yapı kullanılmalıdır.

`Monitor.Enter`, `Monitor.Exit`, `Monitor.Wait`, `Pulse` ve `PulseAll`, klasik `lock` mekanizmasının daha düşük seviyeli biçimini sunar. Özel condition-variable benzeri protokoller kurulabilir; ancak yanlış kullanım kayıp sinyal ve karmaşık kilit etkileşimleri üretebildiği için daha yüksek düzeyli yapılar çoğu uygulamada daha güvenlidir.

## 28. Kilitlenme, açlık, canlı kilit ve çekişme

Bir program veri yarışı taşımadığı hâlde ilerleme hatası taşıyabilir.

**Kilitlenme (deadlock)** için tipik örnek, iki yürütücünün kilitleri ters sırayla almasıdır:

```text
İş A: lock X -> lock Y
İş B: lock Y -> lock X
```

Bunu önlemenin en etkili yollarından biri sistem genelinde sabit bir kilit sırası tanımlamaktır. Ayrıca kilit altında G/Ç yapmak, kullanıcı kodu/callback çağırmak veya bilinmeyen süreli iş yürütmekten kaçınılmalıdır.

**Açlık (starvation)**, bir yürütücünün sürekli başkalarına öncelik verildiği için ilerleyememesidir. **Canlı kilit (livelock)** ise yürütücülerin aktif biçimde birbirine yol verip yine de iş tamamlayamamasıdır.

**Çekişme (contention)** doğruluk hatası değildir fakat gecikmeyi büyütür. Kilit süresini kısaltmak, veriyi parçalara bölmek, immutable snapshot kullanmak veya tek sahipli mesajlaşmaya geçmek çekişmeyi azaltabilir.

## 29. `SemaphoreSlim` ile eşzamanlılık sınırı

Bir kaynak aynı anda birden çok işi kabul edebilir fakat sınırsız eşzamanlılık istemeyebilir. Veritabanı, uzak servis veya dosya sistemi gibi kaynakları korumak için `SemaphoreSlim` pratik bir sınırlandırıcıdır.

```csharp
public sealed class LimitedFetcher
{
    private readonly SemaphoreSlim _slots = new(initialCount: 8);

    public async Task<byte[]> FetchAsync(
        HttpClient client,
        Uri address,
        CancellationToken cancellationToken)
    {
        await _slots.WaitAsync(cancellationToken);
        try
        {
            return await client.GetByteArrayAsync(address, cancellationToken);
        }
        finally
        {
            _slots.Release();
        }
    }
}
```

Bu yapı hem `await` ile uyumludur hem de eşzamanlı çalışan iş sayısını sınırlar. Ancak sıra adaleti veya öncelik gibi daha güçlü gereksinimler varsa özel iş kuyruğu ya da kanal daha uygun olabilir.

## 30. Sinyalleme: olay tutamaçları, geri sayım ve bariyer

Kilit bir kaynağın aynı anda kim tarafından kullanılacağını denetler; sinyalleme ise bir işin başka bir olay gerçekleşene kadar beklemesini koordine eder.

- `AutoResetEvent`, her sinyalde bir bekleyeni serbest bırakır.
- `ManualResetEvent`/`ManualResetEventSlim`, açık kaldığı sürece bekleyenlerin geçmesine izin verir.
- `CountdownEvent`, belirli sayıda tamamlanma sinyali bekler.
- `Barrier`, çok aşamalı paralel algoritmada katılımcıları faz sınırlarında buluşturur.
- `Mutex`, gerektiğinde süreçler arası karşılıklı dışlama sağlayabilir.

Bu yapıların çoğu iş parçacığını bloke eder. Asenkron uygulamada bloke eden wait-handle yaklaşımını doğrudan taşımak yerine görev, kanal veya `SemaphoreSlim.WaitAsync` ile tasarım çoğu zaman daha ölçeklenebilirdir.

## 31. `ReaderWriterLockSlim` ve okuma ağırlıklı durum

Bir veri yapısı çok sık okunup seyrek değiştiriliyorsa aynı anda birden çok okuyucuya izin vermek teorik olarak çekişmeyi azaltabilir. `ReaderWriterLockSlim`, okuma, yazma ve yükseltilebilir okuma kipleri sunar.

```csharp
public sealed class NameCache
{
    private readonly ReaderWriterLockSlim _gate = new();
    private readonly Dictionary<int, string> _items = [];

    public bool TryGet(int id, out string? value)
    {
        _gate.EnterReadLock();
        try
        {
            return _items.TryGetValue(id, out value);
        }
        finally
        {
            _gate.ExitReadLock();
        }
    }

    public void Put(int id, string value)
    {
        _gate.EnterWriteLock();
        try
        {
            _items[id] = value;
        }
        finally
        {
            _gate.ExitWriteLock();
        }
    }
}
```

Okuyucu-yazar kilidi otomatik olarak basit `lock`tan hızlı değildir. Kritik bölüm çok kısa, yazma sık veya çekişme düşükse ek yönetim maliyeti avantajı ortadan kaldırabilir. Ölçüm yapılmadan kullanılmamalıdır.

## 32. Üretici-tüketici akışı, `Channel<T>` ve geri basınç

Klasik producer/consumer problemi, bir tarafın iş üretirken diğer tarafın tüketmesidir. Modern .NET'te `System.Threading.Channels`, bu modeli asenkron okuma/yazma ve birden çok üretici/tüketici desteğiyle doğrudan sağlar.

Sınırlı kanal, üretim tüketimden hızlı olduğunda belleğin sınırsız büyümesini engeller ve **geri basınç** oluşturur:

```csharp
using System.Threading.Channels;

Channel<Job> queue = Channel.CreateBounded<Job>(
    new BoundedChannelOptions(256)
    {
        FullMode = BoundedChannelFullMode.Wait,
        SingleReader = false,
        SingleWriter = false
    });

static async Task ProduceAsync(
    ChannelWriter<Job> writer,
    IEnumerable<Job> jobs,
    CancellationToken cancellationToken)
{
    try
    {
        foreach (Job job in jobs)
            await writer.WriteAsync(job, cancellationToken);
    }
    finally
    {
        writer.TryComplete();
    }
}

static async Task ConsumeAsync(
    ChannelReader<Job> reader,
    CancellationToken cancellationToken)
{
    await foreach (Job job in reader.ReadAllAsync(cancellationToken))
        await ProcessAsync(job, cancellationToken);
}
```

`BlockingCollection<T>` bloklayan producer/consumer akışı için hâlâ kullanılabilir. Baştan sona asenkron bir sistemde kanal çoğu zaman daha doğal bir bileşim sağlar.

## 33. Concurrent koleksiyonlar ve değişmez snapshot'lar

`ConcurrentDictionary<TKey,TValue>`, `ConcurrentQueue<T>`, `ConcurrentStack<T>` ve benzeri türler temel koleksiyon işlemlerini eşzamanlı erişime uygun hâle getirir. Bu, koleksiyon etrafındaki bütün iş kuralının atomik olduğu anlamına gelmez.

Örneğin "varsa oku, koşulu kontrol et, başka sistemi çağır, sonra güncelle" zinciri birkaç thread-safe koleksiyon çağrısından oluşsa bile bütün olarak yarış durumuna açık olabilir. Atomiklik sınırı domain invariant'ına göre belirlenmelidir.

Okuma çok sık ve güncelleme seyrekse immutable koleksiyon veya değişmez snapshot değişimi güçlü bir alternatiftir:

```csharp
private ImmutableDictionary<string, Endpoint> _snapshot =
    ImmutableDictionary<string, Endpoint>.Empty;

public Endpoint? Find(string key)
{
    ImmutableDictionary<string, Endpoint> snapshot =
        Volatile.Read(ref _snapshot);

    return snapshot.GetValueOrDefault(key);
}
```

Yeni snapshot hazırlanıp tek referans değişimiyle yayımlanabilir. Bu yaklaşım okuyucularda kilit ihtiyacını azaltır.

## 34. İptal, zaman aşımı, son tarih ve düzenli kapanış

`CancellationToken` zorla sonlandırma değil, işbirlikçi iptal sinyalidir. Kod güvenli noktada token'ı denetler ve işlemi sonlandırır. İptal ile zaman aşımı da aynı kavram değildir: iptal çağıranın artık sonucu istemediğini, zaman aşımı ise belirlenen süre sınırının aşıldığını ifade edebilir.

Birden çok iptal kaynağı birleştirilebilir:

```csharp
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(10));
using var linked = CancellationTokenSource.CreateLinkedTokenSource(
    applicationStopping,
    requestAborted,
    deadline.Token);

await ProcessAsync(linked.Token);
```

Düzenli kapanış sırası genellikle şöyledir:

1. yeni iş kabulünü durdur,
2. çalışan işlere iptal sinyali ver,
3. sahip olunan görevlerin tamamlanmasını sınırlı süre bekle,
4. kuyruk ve akışları tamamla,
5. kalıcı kaynakları kapat,
6. bitmeyen işleri tanılanabilir biçimde kaydet.

`Thread.Abort` bu modelin alternatifi değildir; modern .NET'te desteklenmez.

## 35. `SynchronizationContext`, `ExecutionContext` ve devamların yeri

Masaüstü kullanıcı arayüzleri çoğu zaman denetimlerin yalnız UI iş parçacığından değiştirilmesini ister. `SynchronizationContext`, bir devamın belirli bağlama gönderilmesini sağlayabilen soyutlamadır. `await`, bulunduğu ortama göre bağlamı yakalayabilir ve devamı tekrar oraya döndürebilir.

Genel amaçlı kitaplık kodunda bağlam yakalamanın gerekli olmadığı yerlerde `ConfigureAwait(false)` kullanılabilir. Uygulama katmanında ise devamın UI bağlamına dönmesi bilinçli bir gereksinim olabilir. ASP.NET Core gibi ortamlarda klasik UI tarzı `SynchronizationContext` varsayımı yapılmamalıdır.

`ExecutionContext` farklı bir kavramdır; `AsyncLocal<T>` gibi ortam durumlarının mantıksal yürütme akışı boyunca taşınmasıyla ilgilidir. `ThreadLocal<T>` fiziksel iş parçacığına, `AsyncLocal<T>` ise mantıksal asenkron akışa bağlıdır.

Bu ayrım request kimliği, trace bilgisi, kültür veya güvenlik bağlamı taşınırken önemlidir.

## 36. `TaskCompletionSource<T>` ile callback/event dünyasını `Task` modeline bağlamak

Bazı API'ler tamamlanmayı callback, event veya özel protokolle bildirir. `TaskCompletionSource<T>`, bu dış tamamlanmayı bir `Task<T>` olarak sunmaya yarar.

```csharp
static Task<string> WaitForMessageAsync(
    LegacyReceiver receiver,
    CancellationToken cancellationToken)
{
    var source = new TaskCompletionSource<string>(
        TaskCreationOptions.RunContinuationsAsynchronously);

    void Handler(object? sender, MessageEventArgs e)
    {
        receiver.MessageReceived -= Handler;
        source.TrySetResult(e.Message);
    }

    receiver.MessageReceived += Handler;

    cancellationToken.Register(() =>
    {
        receiver.MessageReceived -= Handler;
        source.TrySetCanceled(cancellationToken);
    });

    return source.Task;
}
```

`TrySet...` yöntemleri yarışan tamamlanma yollarında ikinci tamamlamayı exception'a dönüştürmez. `RunContinuationsAsynchronously`, tamamlayan thread üzerinde beklenmedik biçimde kullanıcı devamlarının iç içe çalışmasını önlemeye yardımcı olur.

Bu araç, zaten `Task` döndüren bir API'yi gereksiz yere sarmak için kullanılmamalıdır.

## 37. CPU ağırlıklı paralellik: `Parallel`, PLINQ ve görevler

CPU ağırlıklı bağımsız işler çok çekirdeğe bölünebiliyorsa paralellik hız kazandırabilir. İş çok küçükse partitioning, scheduling ve birleştirme maliyeti kazancı aşabilir.

```csharp
double[] input = CreateInput();

Parallel.For(
    fromInclusive: 0,
    toExclusive: input.Length,
    i =>
    {
        input[i] = ExpensiveTransform(input[i]);
    });
```

PLINQ, in-memory sorguların bazılarını paralelleştirebilir:

```csharp
var result = values
    .AsParallel()
    .Where(IsCandidate)
    .Select(Score)
    .Where(static score => score > 0.80)
    .ToArray();
```

Sıra korunması gerektiğinde `AsOrdered` ek maliyet getirir. Yan etkili sorgular, paylaşılan mutable durum veya G/Ç ağırlıklı işlemler PLINQ için doğal aday değildir.

Paralellik derecesi makinenin çekirdek sayısı kadar basit bir sabit olmayabilir; aynı süreçteki başka işler, NUMA/önbellek davranışı ve dış kaynak sınırları da dikkate alınmalıdır.

## 38. Zamanlayıcılar ve periyodik iş

`Timer` türleri callback tabanlıdır ve iş önceki tur bitmeden tekrar tetiklenebiliyorsa örtüşen yürütmeler oluşabilir. Düzenli bir asenkron döngü için `PeriodicTimer` daha okunabilir bir model sunar:

```csharp
static async Task RunPeriodicAsync(CancellationToken cancellationToken)
{
    using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));

    while (await timer.WaitForNextTickAsync(cancellationToken))
    {
        await RefreshSnapshotAsync(cancellationToken);
    }
}
```

Periyodik görevde "her beş saniyede bir başlat" ile "bir iş bittikten beş saniye sonra yeniden başlat" farklı zaman semantiklerine sahiptir. Uzun süren iş, saat kayması, çakışma ve kapanış davranışı açıkça belirlenmelidir.

## 39. Tarihsel API'leri güncel .NET'e taşımak

Eski C# threading kaynaklarının kavramsal çekirdeği değerlidir; API tercihleri ise dönemine göre okunmalıdır.

- `Thread.Abort` modern .NET'te desteklenmez ve `PlatformNotSupportedException` üretir. İşbirlikçi iptal için `CancellationToken` kullanılır.
- `Thread.Suspend` ve `Thread.Resume` kullanımdan kaldırılmıştır; kilit ve koordinasyon yapılarıyla değiştirilmelidir.
- `BackgroundWorker`, olay tabanlı eski masaüstü asenkron modelinin parçasıdır; yeni kodda `Task`, `async` ve `await` genellikle daha doğrudur.
- Delegate üzerindeki `BeginInvoke`/`EndInvoke`, modern .NET'te desteklenmeyen .NET Framework-era uzaktan iletişim altyapısına bağlıdır.
- `WebClient`/`WebRequest` merkezli ağ örnekleri yeni kodda `HttpClient` ile değiştirilmelidir.
- `AppDomain` ile izolasyon modern .NET'te genel uygulama izolasyon modeli değildir; gerektiğinde süreç, container veya uygun yükleme bağlamı kullanılır.
- Bloklayan producer/consumer yapılarının yanında `Channel<T>` ile asenkron geri basınç tasarlanabilir.
- Genel amaçlı `object` kilitleri çalışmaya devam etse de C# 13+ ve .NET 9+ için ayrılmış `System.Threading.Lock` yeni genel kilitleme seçeneğidir.

Tarihsel örneklerin değeri, yarış durumu, paylaşılan durum, sinyalleme, atomiklik ve paralellik maliyetini göstermeleridir. Güncel uygulama ise bu kavramları daha yüksek düzeyli, iptal edilebilir ve asenkron bileşebilir API'lerle kurar.

## 40. HTTP ve ağ istemcisi

Eski `WebRequest`/`WebClient` merkezli kod yerine güncel istemci API'si `HttpClient`'tır. İstemci yaşam döngüsü önemlidir; her istek için yeni istemci üretmek bağlantı havuzunun avantajlarını yok edebilir.

```csharp
using var client = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(5)
};

using HttpResponseMessage response = await client.GetAsync(
    "https://example.com/",
    HttpCompletionOption.ResponseHeadersRead);

response.EnsureSuccessStatusCode();

await using Stream body = await response.Content.ReadAsStreamAsync();
Console.WriteLine(response.StatusCode);
```

Sunucu uygulamalarında `IHttpClientFactory` gibi yönetilen yaşam döngüsü mekanizmaları tercih edilebilir.

## 41. Yansıma, öznitelik ve üst veri

Yansıma, tip ve üyeleri çalışma zamanında incelemeyi sağlar. Güçlüdür ancak doğrudan çağrıdan daha maliyetlidir ve trimming/Native AOT senaryolarında dinamik erişimler ayrıca düşünülmelidir.

```csharp
[AttributeUsage(AttributeTargets.Class)]
public sealed class HandlerAttribute(string name) : Attribute
{
    public string Name { get; } = name;
}

[Handler("telemetry")]
public sealed class TelemetryHandler;

Type type = typeof(TelemetryHandler);
HandlerAttribute? attribute = type
    .GetCustomAttributes(typeof(HandlerAttribute), inherit: false)
    .Cast<HandlerAttribute>()
    .SingleOrDefault();

Console.WriteLine(attribute?.Name);
```

Kaynak üreteçleri veya compile-time registration, yüksek performans ve AOT uyumluluğu gereken durumlarda yansımaya alternatif olabilir.

## 42. Güvensiz kod, işaretçi ve `stackalloc`

C# gerektiğinde unsafe kod ve işaretçi kullanımına izin verir. Bu alan güvenli managed kodun garanti setini daraltır; yalnız ölçülmüş gereksinim olduğunda kullanılmalıdır.

```csharp
static int SumStackAllocated()
{
    Span<int> values = stackalloc int[4] { 3, 5, 7, 11 };
    int sum = 0;

    foreach (int value in values)
    {
        sum += value;
    }

    return sum;
}

Console.WriteLine(SumStackAllocated());
```

Bu örnek işaretçi kullanmadan stack bellek ayırma gösterir. `unsafe` işaretçi erişimi native birlikte çalışabilirlik veya çok özel performans yollarında gerekebilir.

## 43. Operatör aşırı yükleme, dizinleyici ve dönüşümler

Operatör aşırı yükleme, yalnız işlemin anlamı domain içinde doğal olduğunda kullanılmalıdır. Sürpriz semantik okunabilirliği azaltır.

```csharp
public readonly record struct Vector2(double X, double Y)
{
    public static Vector2 operator +(Vector2 left, Vector2 right)
        => new(left.X + right.X, left.Y + right.Y);

    public double this[int index] => index switch
    {
        0 => X,
        1 => Y,
        _ => throw new IndexOutOfRangeException()
    };
}

Vector2 v = new(1, 2) + new Vector2(3, 4);
Console.WriteLine($"{v[0]}, {v[1]}");
```

Implicit dönüşümler veri kaybı veya şaşırtıcı maliyet üretmemelidir. Riskli dönüşümler `explicit` olmalıdır.

## 44. XML belge açıklamaları ve API sözleşmesi

Public API'lerde XML documentation IDE desteği ve dokümantasyon üretimi için kullanılabilir. Ancak yorum, kodun zaten söylediğini tekrar etmek yerine sözleşme, sınır ve hata davranışını açıklamalıdır.

```csharp
/// <summary>0 ile 1 arasında normalize edilmiş oran döndürür.</summary>
/// <exception cref="ArgumentOutOfRangeException">Değer 0..100 aralığında değilse.</exception>
public static double NormalizePercent(int value)
{
    if (value is < 0 or > 100)
    {
        throw new ArgumentOutOfRangeException(nameof(value));
    }

    return value / 100.0;
}
```

## 45. Özyinelemeli inişle basit bir ayrıştırıcı

Dil işleme, yalnız derleyici teorisine ait değildir. Küçük kural dilleri, filtre ifadeleri ve yapılandırma sözdizimleri recursive-descent parser ile okunabilir.

Aşağıdaki örnek toplama ve çarpmanın önceliğini koruyan küçük bir aritmetik ayrıştırıcıdır:

```csharp
public sealed class ExpressionParser
{
    private readonly string _text;
    private int _index;

    public ExpressionParser(string text)
    {
        _text = text;
    }

    public double Parse()
    {
        double value = ParseExpression();
        SkipSpaces();

        if (_index != _text.Length)
        {
            throw new FormatException($"Beklenmeyen karakter: {_text[_index]}");
        }

        return value;
    }

    private double ParseExpression()
    {
        double value = ParseTerm();

        while (true)
        {
            SkipSpaces();

            if (Match('+'))
            {
                value += ParseTerm();
            }
            else if (Match('-'))
            {
                value -= ParseTerm();
            }
            else
            {
                return value;
            }
        }
    }

    private double ParseTerm()
    {
        double value = ParseNumber();

        while (true)
        {
            SkipSpaces();

            if (Match('*'))
            {
                value *= ParseNumber();
            }
            else if (Match('/'))
            {
                value /= ParseNumber();
            }
            else
            {
                return value;
            }
        }
    }

    private double ParseNumber()
    {
        SkipSpaces();
        int start = _index;

        while (_index < _text.Length &&
               (char.IsDigit(_text[_index]) || _text[_index] == '.'))
        {
            _index++;
        }

        if (start == _index ||
            !double.TryParse(
                _text.AsSpan(start, _index - start),
                System.Globalization.NumberStyles.Float,
                System.Globalization.CultureInfo.InvariantCulture,
                out double value))
        {
            throw new FormatException("Sayı bekleniyordu.");
        }

        return value;
    }

    private bool Match(char expected)
    {
        if (_index >= _text.Length || _text[_index] != expected)
        {
            return false;
        }

        _index++;
        return true;
    }

    private void SkipSpaces()
    {
        while (_index < _text.Length && char.IsWhiteSpace(_text[_index]))
        {
            _index++;
        }
    }
}

Console.WriteLine(new ExpressionParser("2 + 3 * 4").Parse());
```

Gerçek bir parser için parantez, unary operatör, hata konumu, tokenization ve limitler ayrıca ele alınmalıdır.

## 46. Masaüstü, web ve uygulama katmanları

Windows Forms ve WPF, Windows masaüstünde yaşamaya devam eder. ASP.NET Core web/API uygulamalarının ana .NET çatısıdır. .NET MAUI mobil ve masaüstü istemciler için ortak bir uygulama modeli sunar. Dil bilgisi bu UI veya web çatılarına bağlı değildir; aynı C# tür sistemi ve çalışma zamanı ilkeleri farklı uygulama modellerinde kullanılır.

UI kodunda iş kurallarını event handler içine yığmak yerine domain ve servis katmanlarını ayrıştırmak test edilebilirliği artırır.

## 47. `required`, `init` ve birincil kurucularla nesne sözleşmesi

Modern C# nesne oluşturmayı yalnız kurucu aşırı yüklemeleriyle sınırlamaz. `init` yalnız başlatma aşamasında atamaya izin verir; `required`, nesne oluşturulurken ilgili üyenin başlatılmasını derleyici sözleşmesine dönüştürür. Birincil kurucular ise sınıf veya struct bildiriminin üzerinde kurucu parametreleri tanımlar.

```csharp
public sealed class ServiceOptions(string endpoint)
{
    public string Endpoint { get; } =
        string.IsNullOrWhiteSpace(endpoint)
            ? throw new ArgumentException("Endpoint boş olamaz.", nameof(endpoint))
            : endpoint;

    public required TimeSpan Timeout { get; init; }
    public int RetryCount { get; init; } = 2;
}

var options = new ServiceOptions("https://api.example.com")
{
    Timeout = TimeSpan.FromSeconds(3)
};
```

`required`, çalışma zamanında otomatik doğrulama yapmaz; başlatma sözleşmesini derleyici düzeyinde güçlendirir. Birincil kurucu parametresi sınıf gövdesinde kullanılıyorsa derleyici gerektiğinde değeri nesne durumunda tutabilir. Bu nedenle kurucu parametresi ile aynı değeri ayrıca alanda saklamak, fark edilmeden iki kopya oluşturabilir.

## 48. `ref struct`, `scoped` ve yaşam süresi güvenliği

`Span<T>` ve `ReadOnlySpan<T>` birer `ref struct` örneğidir. Bu türler yönetilen heap üzerinde keyfî biçimde yaşayamaz; derleyici, tuttukları referansların hedeflerinden daha uzun yaşamamasını denetler. Bu sınırlama düşük seviyeli bellek erişimini güvenli sınırlar içinde tutar.

```csharp
static int ParseHeader(scoped ReadOnlySpan<byte> data)
{
    if (data.Length < 4)
        throw new ArgumentException("Başlık en az 4 bayt olmalıdır.", nameof(data));

    return data[0]
         | (data[1] << 8)
         | (data[2] << 16)
         | (data[3] << 24);
}
```

`scoped`, bir referansın veya `ref struct` değerinin çağrı kapsamı dışına kaçamayacağını ifade etmeye yardımcı olur. `ref readonly` ise büyük değer türlerini kopyalamadan salt okunur başvuruyla döndürmek veya geçirmek için kullanılabilir.

Bu araçlar mikro-optimizasyon olarak değil, yaşam süresi ve kopyalama maliyeti gerçekten önemli olduğunda kullanılmalıdır. Daha basit değer aktarımı aynı işi karşılıyorsa okunabilirlik tercih edilir.

## 49. `ValueTask` ne zaman kullanılmalı?

Asenkron API için varsayılan dönüş türü `Task`/`Task<T>` olmalıdır. `ValueTask<T>`, çağrıların önemli bir bölümünün eşzamanlı tamamlandığı ve `Task` ayırmalarının ölçülmüş bir darboğaz oluşturduğu sıcak yollarda yararlı olabilir.

```csharp
public sealed class LookupCache
{
    private readonly Dictionary<int, string> _cache = [];

    public ValueTask<string?> FindAsync(
        int id,
        CancellationToken cancellationToken = default)
    {
        if (_cache.TryGetValue(id, out string? value))
            return ValueTask.FromResult<string?>(value);

        return LoadSlowAsync(id, cancellationToken);
    }

    private static async ValueTask<string?> LoadSlowAsync(
        int id,
        CancellationToken cancellationToken)
    {
        await Task.Delay(20, cancellationToken);
        return id.ToString();
    }
}
```

`ValueTask` daha karmaşık bir tüketim sözleşmesine sahiptir. Genel kural, dönen değeri doğrudan bir kez `await` etmektir. Birden çok kez bekleme, sonucu erken okuma veya aynı değeri farklı tüketim yollarıyla kullanma güvenli varsayım değildir. Performans ölçümü yoksa `Task` daha basit ve daha doğru varsayılandır.

## 50. Asenkron akışlar: `IAsyncEnumerable<T>`

Tek bir sonuç yerine zaman içinde veri üreten G/Ç kaynakları için `IAsyncEnumerable<T>` doğal bir modeldir. Tüm sonucu bellekte biriktirmek yerine öğeler geldikçe işlenebilir.

```csharp
using System.Runtime.CompilerServices;

static async IAsyncEnumerable<int> ReadSamplesAsync(
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    for (int i = 0; i < 5; i++)
    {
        await Task.Delay(50, cancellationToken);
        yield return i * 10;
    }
}

await foreach (int sample in ReadSamplesAsync(cancellationToken))
{
    Console.WriteLine(sample);
}
```

`await foreach` üretici ile tüketiciyi asenkron biçimde bağlar. Bununla birlikte akışın doğal olarak geri basınçlı olduğu varsayılmamalıdır; üretici ayrı bir kuyrukta sınırsız veri biriktiriyorsa bellek yine büyüyebilir. Yüksek hacimli sistemlerde bounded `Channel<T>` veya açık kapasite politikasıyla birleştirmek gerekebilir.

## 51. `Memory<T>`, `ArrayPool<T>` ve tampon sahipliği

`Span<T>` kısa ömürlü ve yığınla sınırlı yaşam süresi için uygundur; asenkron sınırların ötesinde taşınacak tamponlar için `Memory<T>`/`ReadOnlyMemory<T>` kullanılabilir. Böylece aynı bellek bölgesi kopyalanmadan farklı katmanlar arasında temsil edilebilir.

Sık büyük dizi ayırmak GC baskısı yaratıyorsa `ArrayPool<T>` yeniden kullanılabilir tampon sağlar:

```csharp
using System.Buffers;

static int ReadPrefix(Stream stream)
{
    byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);

    try
    {
        int read = stream.Read(buffer, 0, 4096);
        return read;
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(
            buffer,
            clearArray: true);
    }
}
```

Havuzdan dönen dizinin uzunluğu istenenden büyük olabilir; mantıksal veri uzunluğu ayrıca tutulmalıdır. Hassas veri taşıyan tamponlar geri verilirken temizleme gerekebilir. Havuzlama, nesneleri ücretsiz kılmaz; belleği daha uzun süre elde tutabilir. Bu nedenle yalnız ölçülmüş ayırma baskısında kullanılmalıdır.

## 52. Çöp toplama, büyük nesneler ve sabitleme

.NET çöp toplayıcısı nesil temellidir. Kısa ömürlü nesnelerin çoğu 0. nesilde toplanır; daha uzun yaşayan nesneler üst nesillere taşınır. Büyük nesneler ayrı büyük nesne yığınında tutulur. Varsayılan büyük nesne eşiği 85.000 bayttır ve çalışma zamanı yapılandırmasıyla değiştirilebilir.

Üretim açısından önemli sonuçlar:

- çok sayıda kısa ömürlü küçük nesne, sık genç nesil GC'lerine;
- tekrarlanan büyük tamponlar, büyük nesne yığını ve 2. nesil toplamalarına;
- uzun süre sabitlenen nesneler, heap parçalanmasına;
- gereksiz sonlandırıcılar, nesnelerin yaşam süresinin uzamasına

neden olabilir.

GC'yi iyileştirmenin ilk yolu `GC.Collect()` çağrılarını artırmak değildir. Önce gereksiz ayırmaları, yaşam sürelerini ve büyük geçici nesneleri azaltmak gerekir.

```csharp
static void Fill(Span<byte> destination)
{
    Random.Shared.NextBytes(destination);
}

byte[] buffer = new byte[16 * 1024];
Fill(buffer);
```

Düşük ayırmalı kod için `Span<T>`, havuzlar ve yeniden kullanılabilir tamponlar yararlı olabilir; ancak bunlar sahiplik ve yaşam süresi karmaşıklığını artırdığından yalnız ölçümle doğrulanmış sıcak yollarda uygulanmalıdır.

## 53. Zamanı bağımlılık olarak modellemek: `TimeProvider`

Doğrudan `DateTime.UtcNow` veya gerçek zamanlı `Task.Delay` kullanımı, zaman bağımlı iş kurallarını test etmeyi zorlaştırabilir. .NET'teki `TimeProvider`, zamanı bir bağımlılık olarak taşımaya olanak verir.

```csharp
public sealed class ExpiringValue(TimeProvider timeProvider)
{
    private readonly TimeProvider _timeProvider = timeProvider;
    private DateTimeOffset _expiresAt;

    public void Reset(TimeSpan lifetime)
    {
        _expiresAt = _timeProvider.GetUtcNow() + lifetime;
    }

    public bool IsExpired()
    {
        return _timeProvider.GetUtcNow() >= _expiresAt;
    }
}
```

Bu yaklaşım saat, son kullanma süresi, retry gecikmesi ve benzeri davranışların deterministik testini kolaylaştırır. Sistem saatini doğrudan her katmandan okumak yerine zamanın nerede iş kuralı olduğunu açık hale getirir.

## 54. Yüksek başarımlı akış G/Ç: `System.IO.Pipelines`

Ağ protokolü, sıkıştırma, codec veya büyük akış ayrıştırma gibi senaryolarda `System.IO.Pipelines`, tampon yönetimi ve geri basınçla birlikte yüksek başarımlı G/Ç için tasarlanmıştır. `PipeReader` ve `PipeWriter`, parçalı gelen veriyi birleştirirken gereksiz kopyaları azaltmaya yardımcı olur.

```csharp
using System.IO.Pipelines;

static async Task<int> CountBytesAsync(
    PipeReader reader,
    CancellationToken cancellationToken)
{
    int total = 0;

    while (true)
    {
        ReadResult result = await reader.ReadAsync(cancellationToken);
        ReadOnlySequence<byte> buffer = result.Buffer;

        total += checked((int)buffer.Length);
        reader.AdvanceTo(buffer.End);

        if (result.IsCompleted)
            break;
    }

    await reader.CompleteAsync();
    return total;
}
```

`AdvanceTo` çağrısının yanlış sınırlarla yapılması tüketilmiş veya incelenmiş verinin yönetimini bozabilir. Pipelines düşük seviyeli bir performans aracıdır; sıradan dosya veya HTTP işlemlerinde `Stream` ve yüksek düzeyli API'ler genellikle daha okunabilir seçimdir.

## 55. ThreadPool açlığını ve beklemeyi tanılamak

Eşzamanlılık hataları yalnız doğruluk hatası olarak görülmez; yüksek kuyruk uzunluğu, artan iş parçacığı sayısı ve düşük CPU kullanımı altında ciddi gecikme olarak da ortaya çıkabilir. Sync-over-async kalıbı (`.Result`, `.Wait()`, `.GetAwaiter().GetResult()`), uzun kilit beklemeleri ve bloklayan I/O, ThreadPool çalışanlarını tüketebilir.

.NET tanılama araçları bu ayrımı görünür kılar:

- `dotnet-counters`, çalışma zamanı sayaçlarını canlı gözlemlemek;
- `dotnet-trace`, bekleme ve çalışma zamanı olaylarını zaman içinde toplamak;
- `dotnet-stack` veya dump incelemesi, hangi çağrı yığınlarının çalışanları tuttuğunu görmek

için kullanılabilir.

Tanılama metriği tek başına neden değildir. Yüksek thread sayısını "daha fazla thread verelim" yaklaşımıyla düzeltmek yerine, hangi kodun havuz iş parçacığını bloke ettiğini bulmak gerekir. Asenkron G/Ç yolunu baştan sona `await` ile sürdürmek çoğu sync-over-async sorununda temel çözümdür.

## 56. C# 14 ve .NET 10 ile güncel dil çizgisi

C# 14; extension members, null-conditional assignment, `field` backed property, daha güçlü `Span<T>` dönüşümleri, genişletilmiş `partial` üyeler ve kullanıcı tanımlı compound assignment gibi yenilikler getirir. Bu özellikler temel C# bilgisinin yerine geçmez; var olan tür, bellek ayırma, dispatch ve lifetime kuralları üzerinde çalışır.

```csharp
public sealed class Job
{
    public string? Result { get; set; }
}

Job? current = new();
current?.Result = "done";

Console.WriteLine(current?.Result);
```

Null-conditional assignment C# 14 özelliğidir.

.NET 10 çalışma zamanı JIT inlining, devirtualization, stack bellek ayırma, Native AOT ve kod üretimi tarafında geliştirmeler içerir. Performans davranışı yine ölçülmelidir; yeni çalışma zamanı sürümü her iş yükünde otomatik ve aynı oranda hızlanma anlamına gelmez.

## 57. Eski C#/.NET kodunu güncellerken

Eski kaynakların büyük bölümü dilin temel semantiği açısından hâlâ öğreticidir; fakat API seçimi ve çalışma zamanı varsayımları güncellenmelidir.

- `ArrayList` yerine `List<T>`, `Hashtable` yerine `Dictionary<TKey,TValue>` kullanılır.
- `WebRequest`/`WebClient` yerine `HttpClient` tercih edilir.
- `Thread.Abort`, `Suspend` ve `Resume` yerine işbirlikçi iptal ve görev tabanlı model kullanılır.
- Sonlandırıcı ile doğrudan kaynak yönetimi yerine `IDisposable`, `IAsyncDisposable` ve gerekirse `SafeHandle` kullanılır.
- Dize birleştirme, I/O ve koleksiyon kodunda bellek ayırma davranışı ayrıca değerlendirilir.
- Null olabilir referans türleri ve çözümleyiciler yeni kodda sözleşme hatalarını erken yakalamaya yardımcı olur.
- Yansıma yoğun mimariler kırpma ve Native AOT ile birlikte yeniden değerlendirilir.
- Güvensiz kod, "daha hızlıdır" varsayımıyla değil ölçülmüş gereksinim ve sınırlandırılmış kapsamla kullanılır.

## 58. Performans ve üretim davranışı

C# uygulamasının maliyeti yalnız algoritmanın Big-O sınıfından oluşmaz. Allocation oranı, GC nesilleri, boxing, arayüz üzerinden dinamik çağrı, closure/kapama üretimi, asenkron durum makinesi, çekişme, sistem çağrıları, G/Ç toplulaştırması ve veri yerleşimi gerçek gecikmeyi etkiler.

Yüksek trafikli sistemlerde şu sorular yararlıdır:

- Sıcak yol her çağrıda bellek ayırma yapıyor mu?
- Koleksiyon kapasitesi öngörülebiliyor mu?
- LINQ zinciri okunabilirlik kazandırırken gereksiz enumerator veya ara koleksiyon oluşturuyor mu?
- `Task` üretimi gerçekten asenkron bekleme için mi?
- Paylaşılan kilit altında I/O yapılıyor mu?
- İptal ve zaman aşımı katmanlar arasında taşınıyor mu?
- Serileştirme sırasında gereksiz ara string veya byte[] üretiliyor mu?
- Ölçüm BenchmarkDotNet, profiler veya üretime benzer yük ile doğrulanıyor mu?

Dil özelliği seçimi, okunabilirlik ve bakım maliyeti kadar bellek ayırma, gecikme ve eşzamanlılık davranışıyla da değerlendirilmelidir.

## İlgili dersler

C#'ın diğer dillerle tür sistemi ve çalışma zamanı açısından karşılaştırılması için [Programlama Dilleri](/programlama-dilleri), düşük seviye bellek modeli için [C Programlama](/c-programlama-temelleri) ve [C++ Programlama](/cpp-ile-nesne-yonelimli-programlama), JVM karşılaştırması için [Java Programlama](/java-programlama), yazılım tasarımı için [Yazılım Mühendisliği](/yazilim-muhendisligi-surec-gereksinim-tasarim-kalite) notları birlikte okunabilir.

## Lock-free algoritmada ilerleme garantisini doğru adlandırmak

`Interlocked` kullanmak bir algoritmayı otomatik olarak wait-free yapmaz. **Lock-free**, sistem genelinde en az bir yürütücünün ilerlemeye devam edeceğini; **wait-free** ise her yürütücünün sonlu sayıda kendi adımından sonra tamamlanacağını ifade eden daha güçlü bir garantidir. **Obstruction-free** ise bir yürütücü tek başına yeterince uzun çalışırsa ilerleyebilmesini garanti eder.

CAS döngüsünde yoğun çekişme altında aynı thread tekrar tekrar başarısız olabilir; sistem ilerlerken bireysel starvation mümkündür. Bu nedenle "kilit yok = herkes hızlı ilerler" sonucu çıkarılmamalıdır.

Kilitli ve kilitsiz tasarım arasında seçim yaparken yalnız uncontended mikrobenchmark değil; çekişme, adalet, kod doğrulanabilirliği ve tail latency birlikte ölçülmelidir. Basit bir `lock` çoğu iş kuralında karmaşık bir lock-free yapıdan daha güvenilir olabilir.

## Kaynaklar

- Microsoft. *C# Guide*. https://learn.microsoft.com/dotnet/csharp/
- Microsoft. *C# Language Reference*. https://learn.microsoft.com/dotnet/csharp/language-reference/
- Microsoft. *What's new in C# 14*. https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14
- Microsoft. *What's new in .NET 10*. https://learn.microsoft.com/dotnet/core/whats-new/dotnet-10/
- Microsoft. *.NET API Browser*. https://learn.microsoft.com/dotnet/api/
- ECMA International. *ECMA-334: C# Language Specification, 7th Edition*. https://ecma-international.org/publications-and-standards/standards/ecma-334/
- ECMA International. *ECMA-335: Common Language Infrastructure*. https://ecma-international.org/publications-and-standards/standards/ecma-335/
- Jeffrey Richter. *CLR via C#*, 4th Edition. Microsoft Press, 2012.
- Microsoft. *Managed Threading Best Practices*. https://learn.microsoft.com/dotnet/standard/threading/managed-threading-best-practices
- Microsoft. *Threading Objects and Features*. https://learn.microsoft.com/dotnet/standard/threading/threading-objects-and-features
- Microsoft. *The lock statement*. https://learn.microsoft.com/dotnet/csharp/language-reference/statements/lock
- Microsoft. *System.Threading.Channels library*. https://learn.microsoft.com/dotnet/core/extensions/channels
- Microsoft. *Asynchronous programming with async and await*. https://learn.microsoft.com/dotnet/csharp/asynchronous-programming/
- Joseph Albahari. *Threading in C#*. Updated 2011-04-27. https://www.albahari.com/threading/
- Microsoft. *Primary constructors*. https://learn.microsoft.com/dotnet/csharp/whats-new/tutorials/primary-constructors
- Microsoft. *ref struct types*. https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/ref-struct
- Microsoft. *Asynchronous programming scenarios*. https://learn.microsoft.com/dotnet/csharp/asynchronous-programming/async-scenarios
- Microsoft. *Generate and consume async streams*. https://learn.microsoft.com/dotnet/csharp/asynchronous-programming/generate-consume-asynchronous-stream
- Microsoft. *Memory<T> and Span<T> usage guidelines*. https://learn.microsoft.com/dotnet/standard/memory-and-spans/memory-t-usage-guidelines
- Microsoft. *.NET garbage collection*. https://learn.microsoft.com/dotnet/standard/garbage-collection/
- Microsoft. *Large object heap*. https://learn.microsoft.com/dotnet/standard/garbage-collection/large-object-heap
- Microsoft. *TimeProvider overview*. https://learn.microsoft.com/dotnet/standard/datetime/timeprovider-overview
- Microsoft. *System.IO.Pipelines*. https://learn.microsoft.com/dotnet/standard/io/pipelines
- Microsoft. *Debug ThreadPool starvation*. https://learn.microsoft.com/dotnet/core/diagnostics/debug-threadpool-starvation

## Bu Çalışmaya Atıf

Köker, M. A. (2015). C# Programlama. alikoker.com.tr. https://alikoker.com.tr/csharp-programlama

- BibTeX: https://alikoker.com.tr/csharp-programlama.bib
- RIS: https://alikoker.com.tr/csharp-programlama.ris
- CSL-JSON: https://alikoker.com.tr/csharp-programlama.csl.json
