Hap Bilgi 16: C# .NET İpuçları - Daha Temiz, Daha Hızlı ve Daha Verimli Kod Yazmak

Developer hap bilgi sever; 16. bölüm: C# .NET İpuçları - Daha Temiz, Daha Hızlı ve Daha Verimli Kod Yazmak
C# ve .NET ekosistemi bize oldukça güçlü araçlar sunuyor. Ama iyi bir .NET uygulaması yalnızca çalışan koddan ibaret değildir. Okunabilir olmalı, bakımı kolay olmalı, performanslı olmalı.
Bu yazıda gerek geliştirmelerde gerek review süreçlerinde kullanmaya çalıştığım, dikkat ettiğim pratik önerileri 12 başlık altında inceleyeceğiz.
1. Gereksiz Allocation Üretme
.NET performansında en sık gözden kaçan konulardan biri gereksiz bellek tüketimidir.
Her gereksiz object creation, garbage collector üzerinde ekstra yük oluşturur.
Özellikle yoğun trafik alan API’lerde küçük görünen allocation problemleri zamanla ciddi performans maliyetine dönüşebilir.
// ❌ Kötü — gerçekten listeye ihtiyaç yoksa ToList() gereksizdir
var result = items.Select(x => new ItemDto
{
Id = x.Id,
Name = x.Name
}).ToList();
// ✅ Daha iyi — materialization ihtiyaç anına ertelenir
var result = items.Select(x => new ItemDto
{
Id = x.Id,
Name = x.Name
});
Temel soru şu olmalı: Bu nesne gerçekten şimdi gerekli mi?
Gereksiz ToList(), ToArray(), string concat, boxing ve büyük object creation işlemleri mümkün olduğunca azaltılmalıdır.
Özellikle döngü içinde string birleştirme yapıyorsanız StringBuilder tercih edin; her + operasyonu yeni bir heap allocation üretir.
// ❌ Kötü — her iterasyonda yeni string nesnesi
var result = "";
foreach (var item in items)
result += item + ", ";
// ✅ İyi — tek buffer
var sb = new StringBuilder();
foreach (var item in items)
sb.Append(item).Append(", ");
2. Async Kodda Blocking Çağrılardan Kaçın
.NET uygulamalarında async/await kullanmak tek başına yeterli değildir.
Önemli olan async akışını bozmamaktır.
// ❌ Kötü — thread blocking, deadlock riski
var result = GetCustomerAsync().Result;
var result = GetCustomerAsync().GetAwaiter().GetResult();
// ✅ İyi — async zincir korunur
var result = await GetCustomerAsync();
Async zincir mümkün olduğunca baştan sona korunmalıdır.
Controller
↓
Service
↓
Repository
↓
External API / Database
Bir yerde blocking çağrı yapılırsa tüm async modelin faydası azalır.
Async kod yazıyorsan, async akışını bozma.
3. async void Kullanmayın
async void metodlarda fırlayan exception’lar caller tarafından yakalanamaz; global UnhandledException handler’ına düşer ve genellikle uygulamayı çökertir.
// ❌ Kötü — exception kaybolur, test edilemez
public async void ProcessAsync()
{
await DoWorkAsync();
}
// ✅ İyi — caller await edebilir, exception yönetilebilir
public async Task ProcessAsync()
{
await DoWorkAsync();
}
İstisna: UI event handler’ları (Button.Click += async (s, e) => { ... }). Sadece burada async void zorunludur ve kabul edilebilirdir.
4. CancellationToken Kullan
Bir HTTP request iptal edildiğinde arka planda devam eden işlem çoğu zaman gereksizdir.
Özellikle database query, external API çağrısı ve uzun süren operasyonlarda CancellationToken kullanılmalıdır.
public async Task<CustomerDto> GetCustomerAsync(
int customerId,
CancellationToken cancellationToken = default)
{
var customer = await _customerRepository
.GetByIdAsync(customerId, cancellationToken);
return _mapper.Map<CustomerDto>(customer);
}
ASP.NET Core, CancellationToken parametresini controller action’larında otomatik inject eder. Client bağlantıyı kopardığında token iptal olur, sorgu gereksiz yere çalışmaz.
Bu yaklaşım; gereksiz kaynak tüketimini azaltır, timeout yönetimini kolaylaştırır ve sistemi boşuna çalıştırmaz.
5. EF Core — AsNoTracking() ve Projeksiyon
Sadece veri okuyorsanız ve güncellemeyecekseniz change tracker’ı devre dışı bırakın.
// ❌ Kötü — gereksiz tracking overhead, tüm kolonlar çekilir
var customers = await _context.Customers
.Where(c => c.IsActive)
.ToListAsync();
// ✅ İyi — sadece gerekli kolonlar, tracking yok
var customers = await _context.Customers
.AsNoTracking()
.Where(c => c.IsActive)
.Select(c => new CustomerDto { Id = c.Id, Name = c.FullName })
.ToListAsync();
Üretilen SQL’i loglama ile doğrulayın. Kaç kolon çekildiğini görmek çoğu zaman sürpriz yaratır.
AsNoTracking() global olarak açıksa, güncelleme yapacağınız sorgularda AsTracking() ile geri açmayı unutmayın.
6. Magic Number ve Magic String Kullanma
Kod içinde anlamı belli olmayan değerler uzun vadede bakım maliyeti oluşturur.
// ❌ Kötü — 3 neyi temsil ediyor?
if (customer.Status == 3) { }
// ✅ İyi
public enum CustomerStatus { Pending = 1, Approved = 3, Rejected = 5 }
if (customer.Status == CustomerStatus.Approved) { }
String değerler için de aynı kural geçerlidir.
// ❌ Kötü
var key = "customer:" + customerId;
// ✅ İyi
public static class CacheKeys
{
public const string CustomerPrefix = "customer:";
}
Kod kendi niyetini anlatmalı. Magic value ne kadar azalırsa, okunabilirlik o kadar artar.
7. Dependency Injection Lifetime’larını Bilerek Kullan
.NET projelerinde DI kullanmak standart hale geldi. Ama asıl önemli konu, doğru lifetime seçmektir.
| Lifetime | Kullanım Amacı |
|---|---|
Singleton | Uygulama boyunca tek instance |
Scoped | Request boyunca tek instance |
Transient | Her ihtiyaçta yeni instance |
Dikkat edilmesi gereken kritik durum:
Singleton servis
↓
Scoped servisi doğrudan kullanırsa
↓
Captive dependency (lifetime) problemi oluşur
Özellikle DbContext gibi scoped servislerin singleton servisler içinde doğrudan tutulması risklidir.
DI sadece bağımlılık vermek değildir; bağımlılığın yaşam süresini doğru yönetmektir.
8. LINQ’i Dengeli Kullan
LINQ güçlüdür. Ama her zaman iki şeye dikkat edilmelidir: sıralama ve okunabilirlik.
Filtre mümkün olduğunca erken uygulanmalıdır.
// ❌ Kötü — önce tüm listeyi dönüştür, sonra filtrele
var result = orders
.Select(o => new OrderDto(o))
.Where(dto => dto.IsActive);
// ✅ İyi — önce filtrele, sadece kalan nesneleri dönüştür
var result = orders
.Where(o => o.IsActive)
.Select(o => new OrderDto(o));
İş kuralı karmaşıklaştığında tek satıra sığdırmak yerine ara değişkenler kullanın.
// Okunabilir yapı
var activeCustomers = customers.Where(c => c.IsActive);
var highValueCustomers = activeCustomers
.Where(c => c.Orders.Any(o => o.TotalAmount > 1000));
var result = highValueCustomers
.Select(c => new CustomerDto { Id = c.Id, Name = c.Name })
.OrderByDescending(c => c.OrderCount)
.ToList();
Kısa kod her zaman temiz kod değildir. Temiz kod, niyeti en hızlı anlatan koddur.
Ayrıca FirstOrDefault ile SingleOrDefault arasındaki semantik farkı bilin.
// Birden fazla sonuç bekleniyor, ilkini al
var latest = orders.OrderByDescending(o => o.Date).FirstOrDefault();
// Tam olarak 0 ya da 1 sonuç beklenir — fazlası exception fırlatır
var user = users.SingleOrDefault(u => u.Id == userId);
SingleOrDefault, veri tutarsızlığını erken yakalamanın en temiz yoludur.
9. Logging’i Structured Yap
Log mesajları sadece okunmak için değil, aranmak ve analiz edilmek için de yazılır.
// ❌ Kötü — string concatenation, index edilemez
_logger.LogInformation("Customer found: " + customerId);
// ✅ İyi — structured property, log sistemleri indexleyebilir
_logger.LogInformation(
"Customer found. CustomerId: {CustomerId}", customerId);
Yüksek trafikli sistemlerde source-generated logging tercih edilebilir.
public static partial class CustomerLogMessages
{
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "Customer found. CustomerId: {CustomerId}")]
public static partial void CustomerFound(ILogger logger, int customerId);
}
Log, production’daki en yakın arkadaşındır. Ona iyi davran.
10. Exception Yönetimini Sessizce Yutma
Exception yakalamak iyidir. Ama exception’ı sessizce yutmak çoğu zaman kötüdür.
// ❌ Kötü — hata saklanır, production'da ne olduğu anlaşılamaz
try { await ProcessAsync(); }
catch { return false; }
// ✅ İyi — loglanır, OperationCanceledException tekrar fırlatılır
try
{
await ProcessAsync();
}
catch (OperationCanceledException)
{
throw; // CancellationToken senaryolarında tekrar fırlat
}
catch (Exception ex)
{
_logger.LogError(ex, "Process failed.");
return false;
}
Hata yönetimi, hatayı gizlemek değil; kontrollü şekilde görünür kılmaktır.
11. Guard Clause Kullan
İç içe geçmiş if blokları kodun okunabilirliğini azaltır, asıl iş kuralını gizler.
// ❌ Kötü — yoğun iç içe yapı
public void CreateOrder(Customer customer)
{
if (customer != null)
{
if (customer.IsActive)
{
if (customer.HasValidAddress)
{
// Create order
}
}
}
}
// ✅ İyi — guard clause ile erken çıkış
public void CreateOrder(Customer customer)
{
if (customer is null)
throw new ArgumentNullException(nameof(customer));
if (!customer.IsActive)
throw new InvalidOperationException("Customer is not active.");
if (!customer.HasValidAddress)
throw new InvalidOperationException("Customer address is not valid.");
// Create order — happy path görünür
}
Önce geçersiz durumları ele
↓
Sonra ana akışı çalıştır
Happy path görünür olmalı.
12. record ile Value Object Tanımla, Methodları Küçük Tut
Domain’deki değer nesnelerini class ile modellemek boilerplate kod üretir. C# 9+ record bu sorunu ortadan kaldırır.
// ❌ Kötü — Equals, GetHashCode, == override'ları gerekir
public class Money { ... }
// ✅ İyi — structural equality ücretsiz gelir
public record Money(decimal Amount, string Currency);
var price = new Money(99.90m, "TRY");
var same = new Money(99.90m, "TRY");
var discounted = price with { Amount = 79.90m }; // immutable kopya
Console.WriteLine(price == same); // true
Ve son olarak: methodları küçük ve niyet odaklı tutun.
// Hikâyeyi anlatan method — detaylar ayrı metodlarda
public async Task CompleteProcessAsync(ProcessInput input)
{
ValidateInput(input);
var customer = await GetCustomerAsync(input.CustomerId);
await CheckCustomerEligibilityAsync(customer);
var process = CreateProcess(customer, input);
await SaveProcessAsync(process);
await NotifyCustomerAsync(customer);
}
Eğer bir method’a isim koymakta zorlanıyorsanız, o method muhtemelen birden fazla iş yapıyordur.
Özet
| # | Öneri | Kazanım |
|---|---|---|
| 1 | Gereksiz allocation üretme (StringBuilder, lazy eval) | Daha düşük memory pressure |
| 2 | Async blocking yapma (.Result, .GetAwaiter().GetResult()) | Daha iyi scalability |
| 3 | async void yerine async Task kullan | Test edilebilir, exception yönetilebilir |
| 4 | Her async metoda CancellationToken ekle | Gereksiz işlem tüketimini azalt |
| 5 | AsNoTracking() + projeksiyon kullan | Daha az veri transferi ve memory |
| 6 | Magic value kullanma — enum veya const tercih et | Daha okunabilir kod |
| 7 | DI lifetime’larını doğru seç | Güvenli dependency yönetimi |
| 8 | LINQ’de önce Where, sonra Select; Single vs First ayırt et | Okunabilir ve performanslı sorgu |
| 9 | Structured logging kullan | Kolay production analizi |
| 10 | Exception’ı yutma, logla ve OperationCanceledException’ı tekrar fırlat | Görünür hata yönetimi |
| 11 | Guard clause kullan | Temiz akış, görünür happy path |
| 12 | record ile value object, küçük ve niyet odaklı methodlar | Sürdürülebilir tasarım |
Modern .NET uygulamaları için basit formül:
İyi C# kodu = Okunabilirlik + Doğru async kullanım + Kontrollü memory yönetimi + Test edilebilir tasarım + Gözlemlenebilirlik
Hap Bilgi
“Çalışan kod ilk adımdır. Temiz, ölçülebilir, test edilebilir ve sürdürülebilir kod ise profesyonel yazılım geliştirmenin asıl farkıdır.”
C# ve .NET bize güçlü araçlar verir. Bu araçları doğru kullanmak, uygulamanın hem bugünkü performansını hem de yarınki bakım maliyetini belirler.
Her zaman olduğu gibi kaynaklara mutlaka göz atılmasını tavsiye ediyorum. ⬇️
- ASP.NET Core Best Practices — Microsoft https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices
- C# Coding Conventions — Microsoft https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions
- Dependency Injection Guidelines — Microsoft https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/guidelines
- High-performance Logging in .NET — Microsoft https://learn.microsoft.com/en-us/dotnet/core/extensions/logging/high-performance-logging
- Performance best practices in C# — Microsoft https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/performance/
- Memory Management and Patterns in ASP.NET Core — Microsoft https://learn.microsoft.com/en-us/aspnet/core/performance/memory
- EF Core Performance — Microsoft https://learn.microsoft.com/en-us/ef/core/performance/

