C# 12, .NET 8 ile birlikte Kasım 2023'te yayınlandı. Dilin okunabilirliğini ve geliştirici deneyimini iyileştiren bu sürüm, özellikle büyük kod tabanlarında fark yaratan birkaç önemli yenilik getirdi.
1. Primary Constructors
C# 12'nin en çok konuşulan özelliği primary constructors oldu. Sınıf ve struct'ların parametre listesini doğrudan tanım satırına yazabiliyorsunuz.
Eski Yöntem
public class OrderService
{
private readonly IOrderRepository _repository;
private readonly ILogger<OrderService> _logger;
private readonly IEmailService _email;
public OrderService(
IOrderRepository repository,
ILogger<OrderService> logger,
IEmailService email)
{
_repository = repository;
_logger = logger;
_email = email;
}
public async Task<Order?> GetByIdAsync(int id)
{
_logger.LogInformation("Order {Id} isteniyor", id);
return await _repository.FindAsync(id);
}
}
Primary Constructor ile
public class OrderService(
IOrderRepository repository,
ILogger<OrderService> logger,
IEmailService email)
{
public async Task<Order?> GetByIdAsync(int id)
{
logger.LogInformation("Order {Id} isteniyor", id);
return await repository.FindAsync(id);
}
public async Task CompleteAsync(int id)
{
var order = await repository.FindAsync(id) ?? throw new NotFoundException(id);
order.Complete();
await repository.SaveAsync(order);
await email.SendConfirmationAsync(order.CustomerEmail);
}
}
Dikkat: Primary constructor parametreleri field değil, capture edilen değişkenlerdir. Eğer parametreyi hem constructor hem başka bir method kullanıyorsa C# onu otomatik olarak saklar. Ama sadece constructor'da kullanılıyorsa fazladan alan ayrılmaz.
Record ile Fark
// Record: otomatik property üretir, eşitlik karşılaştırması yapar
public record Point(double X, double Y);
// Class primary ctor: sadece parametre alır, property üretmez
public class Point(double x, double y)
{
public double X { get; } = x; // Elle tanımlamak gerekir
public double Y { get; } = y;
public double Distance => Math.Sqrt(X * X + Y * Y);
}
2. Collection Expressions
Koleksiyon başlatma sözdizimi [...] ile birleşik hale geldi. Aynı sözdizimi array, List, Span ve daha fazlası için çalışıyor.
// Dizi
int[] primes = [2, 3, 5, 7, 11, 13];
// List<T>
List<string> cities = ["Ankara", "İstanbul", "İzmir"];
// ImmutableArray
ImmutableArray<int> scores = [100, 95, 87, 72];
// Span<T> — stack allocation
Span<byte> buffer = [0x01, 0x02, 0x03, 0xFF];
// Spread operatörü (..) ile birleştirme
int[] evens = [2, 4, 6];
int[] odds = [1, 3, 5];
int[] all = [..evens, ..odds, 7, 8]; // [2, 4, 6, 1, 3, 5, 7, 8]
// Metot parametresi olarak
void PrintAll(IEnumerable<string> items) { /* ... */ }
PrintAll(["bir", "iki", "üç"]);
Derleyici hedef türe bakarak en uygun koleksiyon tipini seçiyor. Bu sayede new List<string>() { ... } yerine sade [...] yazmak yeterli.
3. Inline Arrays
Performans kritik senaryolarda sabit boyutlu, stack üzerinde yaşayan diziler tanımlanabiliyor. Span<T> ile çalışan her API bunu kullanabilir.
[System.Runtime.CompilerServices.InlineArray(16)]
public struct SixteenByteBuffer
{
private byte _element0; // Tek field yeterli; derleyici gerisini halleder
}
// Kullanım
SixteenByteBuffer buf = default;
Span<byte> span = buf;
span[0] = 0xAB;
span[1] = 0xCD;
// Örnek: küçük ID listesi için heap allocation yok
[InlineArray(8)]
private struct SmallIdBuffer { private int _; }
SmallIdBuffer ids = default;
ids[0] = 1001;
ids[1] = 1002;
Inline arrays, özellikle networking, serialization ve parser kütüphanelerinde belleği heap'e çıkarmadan işlemek için kullanılıyor.
4. Default Lambda Parameters
Lambda ifadelerine artık varsayılan parametre değeri verilebiliyor:
// Basit örnek
var multiply = (int x, int y = 2) => x * y;
Console.WriteLine(multiply(5)); // 10
Console.WriteLine(multiply(5, 3)); // 15
// Gerçek kullanım: isteğe bağlı sayfalama
Func<int, int, IEnumerable<T>> Paginate<T>(IEnumerable<T> source)
=> (int page = 1, int size = 20) => source.Skip((page - 1) * size).Take(size);
// null varsayılan değer
var log = (string message, string? level = null) =>
Console.WriteLine($"[{level ?? "INFO"}] {message}");
5. Alias Any Type
C# 12 öncesinde using alias sadece namespace ve sınıf isimlerine uygulanabiliyordu. Artık tuple, pointer ve primitive dahil her türe alias verilebiliyor:
// Tuple alias
using Point = (double X, double Y);
using Rect = (double Left, double Top, double Right, double Bottom);
Point origin = (0, 0);
Rect viewport = (0, 0, 1920, 1080);
// Karmaşık generic alias
using StringMap = Dictionary<string, List<string>>;
StringMap tags = new() { ["C#"] = ["oop", "functional"] };
// Güvenli array alias
using ByteBlock = byte[];
6. ref readonly Parametreler
Büyük struct'ları kopyalamadan metoda geçirmek için ref readonly kullanılıyordu ama call site'ta ref yazmak zorundaydınız. C# 12 bunu daha esnek hale getirdi:
// Kopyalama olmadan geçirme, ama değişiklik de yok
void Analyze(ref readonly Matrix4x4 matrix)
{
// matrix değiştirilemez, ama kopyalanmadı
Console.WriteLine(matrix.M11);
}
// C# 12'de: ref yazmadan da çağrılabilir
var m = Matrix4x4.Identity;
Analyze(in m); // 'in' ile de çalışır
Analyze(m); // Doğrudan da geçirilebilir (warning verebilir)
Sonuç
C# 12 büyük paradigma değişikliği getirmek yerine günlük geliştiriciyi rahatlatmaya odaklandı. Primary constructors DI ağır projelerde onlarca satır boilerplate kaldırıyor. Collection expressions ise farklı koleksiyon türleri için tutarlı bir sözdizimi sağlıyor. .NET 8 LTS sürümüyle birlikte gelen bu özellikler, production projelerinizde güvenle kullanılabilir.