Bu doküman, modern .NET (6/7/8/9, cross-platform) kullanan bir şirketin senior pozisyonu için hazırlanmış kapsamlı bir mülakat rehberidir. C# ve .NET çekirdek konuları, LINQ, ASP.NET Core, Entity Framework Core ve SQL Server başlıklarını içeriyor, ayrıca senior seviyede genellikle beklenen mimari ve davranışsal soruları da kapsıyor.
Her başlık altında önce konunun kısa bir özeti var, ardından o konuyla ilgili en sık sorulan kritik sorular ve net cevapları yer alıyor. Sonunda örnek kodlama soruları, senin soracağın sorular ve son hafta kontrol listesi bulunuyor.
1. C# ve .NET Çekirdek Konuları
CLR ve Bellek Yönetimi
.NET’te bellek CLR (Common Language Runtime) tarafından yönetilir. Garbage Collector, nesneleri generation’lara ayırır: Gen0 (kısa ömürlü nesneler, en sık toplanan), Gen1 (ara katman), Gen2 (uzun ömürlü nesneler). Büyük nesneler (85KB üzeri) Large Object Heap’te (LOH) tutulur ve farklı bir toplama stratejisine tabidir.
S: Value type ile reference type arasındaki fark nedir, bu fark pratikte neden önemli?
C: Value type’lar (struct, int, bool gibi) doğrudan stack’te (veya bir nesnenin içinde ise heap’te o nesnenin parçası olarak) tutulur ve kopyalandıklarında tüm veri kopyalanır. Reference type’lar (class) heap’te tutulur, değişken sadece bir referans taşır. Bir struct’ı bir metoda parametre olarak geçtiğinde varsayılan olarak kopyalanır, class’ı geçtiğinde aynı nesneye işaret eden referans geçilir. Bu farkı bilmemek, beklenmedik mutasyon bug’larına (örneğin bir struct listesindeki elemanı değiştirmeye çalışıp aslında kopya üzerinde değişiklik yapmak) yol açabilir.
S: Boxing ve unboxing nedir, performansa etkisi ne?
C: Boxing, bir value type’ı (örneğin int) bir reference type’a (object) sarmalama işlemidir, bu sırada heap’te yeni bir alan ayrılır. Unboxing bunun tersidir. Sık boxing/unboxing, özellikle döngü içinde, gereksiz heap allocation’ı ve GC baskısı yaratır. Generic koleksiyonlar (List<int> gibi) bu maliyeti generic olmayan koleksiyonlara (ArrayList) göre büyük ölçüde ortadan kaldırır çünkü tip bilgisini derleme zamanında bilirler ve boxing gerektirmezler.
S: IDisposable ve using bloğu ne işe yarar, neden önemli?
C: Yönetilmeyen kaynakları (dosya handle, veritabanı bağlantısı, socket) tutan sınıflar IDisposable implemente eder ve Dispose() metodu bu kaynakları serbest bırakır. using bloğu, blok bittiğinde (exception fırlasa bile) Dispose()‘un otomatik çağrılmasını garanti eder. Bunu manuel try-finally ile yapmak yerine using kullanmak, kaynak sızıntılarını (resource leak) önlemenin standart yoludur.
Async/Await ve Concurrency
async/await, I/O bound işlemleri thread’i bloklamadan yürütmeyi sağlar. Task ve ValueTask arasındaki fark, ConfigureAwait kullanımı ve deadlock riskleri senior seviyede sık sorulan konulardır.
S: async void yerine neden async Task kullanmalısın?
C: async void metodların exception’ları normal try-catch ile yakalanamaz, doğrudan SynchronizationContext‘e fırlatılır ve genelde uygulamayı çökertir. Ayrıca async void metodlar await edilemez, yani çağıran taraf işlemin ne zaman bittiğini bilemez. async Task hem exception handling’i doğru yapmanı sağlar hem de metodun tamamlanmasını beklemene izin verir. async void sadece event handler’lar gibi zorunlu durumlarda kullanılmalı.
S: ConfigureAwait(false) ne işe yarar, ne zaman kullanılır?
C: await sonrası devam eden kod, varsayılan olarak orijinal SynchronizationContext‘e (örneğin ASP.NET’in eski senkron context’i ya da UI thread’i) geri dönmeye çalışır. ConfigureAwait(false), bu geri dönüşü atlayarak devam eden kodun herhangi bir thread pool thread’inde çalışmasına izin verir. Kütüphane kodu yazarken (UI veya belirli bir context’e bağımlılığın olmadığı yerlerde) bu, gereksiz context switching’i önler ve bazı deadlock senaryolarını (senkron kod içinden .Result veya .Wait() ile async metod çağrılması) engelleyebilir. Modern ASP.NET Core’da SynchronizationContext olmadığı için bu risk daha düşük, ama kütüphane geliştirirken hâlâ iyi bir pratik.
S: Task.Run ile doğrudan async metod çağırmak arasındaki fark nedir?
C: async bir metod I/O bound bir işlemse (veritabanı çağrısı, HTTP isteği gibi), thread’i bloklamadan bekler; thread pool’dan ekstra bir thread almaz. Task.Run, CPU bound bir işi thread pool’a devretmek için kullanılır ve gerçekten bir thread meşgul eder. I/O bound bir işlemi Task.Run içine sarmak (örneğin bir HTTP çağrısını) gereksiz yere bir thread pool thread’i tüketir, bu yaygın bir anti-pattern’dir.
2. LINQ
LINQ, koleksiyonlar ve veri kaynakları üzerinde SQL benzeri sorgulama yapmayı sağlar. Deferred execution mantığı ve IEnumerable ile IQueryable arasındaki fark, LINQ’ün en kritik ve en sık yanlış anlaşılan konusudur.
S: LINQ’te deferred execution (ertelenmiş çalıştırma) ne demek, neden önemli?
C: Where, Select gibi çoğu LINQ metodu, çağrıldığı anda sorguyu çalıştırmaz, sadece sorgunun tanımını oluşturur. Sorgu, sonuç gerçekten kullanılana kadar (foreach, .ToList(), .Count() gibi bir işlemle) çalıştırılmaz. Bu, aynı sorguyu birden fazla kez enumerate etmenin (özellikle veritabanına giden bir IQueryable için) her seferinde sorguyu tekrar çalıştırabileceği anlamına gelir, bu da beklenmedik performans sorunlarına yol açabilir. .ToList() ile sonucu bir kez materialize etmek bu riski ortadan kaldırır.
S: IEnumerable ile IQueryable arasındaki fark nedir?
C: IEnumerable üzerindeki LINQ sorguları bellek içinde (in-memory) çalışır, koleksiyon zaten tamamen yüklenmiş demektir. IQueryable, Entity Framework gibi bir ORM üzerinden geldiğinde, sorgu ifadesini (expression tree) SQL’e çevirip veritabanında çalıştırır, sadece gereken veriyi çeker. IQueryable üzerinde .Where() çağırmak veritabanı tarafında filtreleme yaparken, önce .ToList() yapıp sonra .Where() çağırmak tüm veriyi belleğe çekip filtrelemeyi bellekte yapar; bu, senior seviyede sıkça sorulan ve gerçek projelerde performans farkı yaratan bir ayrımdır.
S: Select ile SelectMany arasındaki fark nedir?
C: Select, her elemanı bire bir dönüştürür, sonuçta orijinal koleksiyonla aynı sayıda eleman olur (iç içe koleksiyonlar varsa sonuç da iç içe kalır). SelectMany, iç içe koleksiyonları düzleştirir (flatten), örneğin her müşterinin sipariş listesini tek bir düz sipariş listesine dönüştürmek için kullanılır.
3. ASP.NET Core ve .NET Framework Yapısı
Dependency Injection ve Servis Ömürleri
.NET Core’un yerleşik DI container’ı üç temel servis ömrü sunar: Transient, Scoped, Singleton. Bu ayrımı doğru bilmek, özellikle web uygulamalarında ciddi hatalardan kaçınmak için kritik.
S: Transient, Scoped ve Singleton servis ömürleri arasındaki fark nedir?
C: Transient, her istek edildiğinde yeni bir örnek oluşturur, en hafif ama en sık allocation yapan seçenektir. Scoped, aynı HTTP isteği (request) içinde tek bir örnek paylaşır, farklı isteklerde farklı örnekler oluşur; DbContext genelde Scoped olarak kaydedilir. Singleton, uygulama boyunca tek bir örnek yaşar, tüm isteklerde paylaşılır. Bir Singleton servisin içine Scoped bir servisi (örneğin DbContext) enjekte etmeye çalışmak “captive dependency” hatasına yol açar, çünkü Singleton’ın ömrü boyunca aynı Scoped örneği tutulmuş olur, bu da veritabanı bağlantısının beklenmedik şekilde uzun süre açık kalmasına neden olabilir.
S: Middleware pipeline nasıl çalışır, sırası neden önemli?
C: ASP.NET Core’da her HTTP isteği, Program.cs içinde app.Use...() çağrılarıyla tanımlanan bir middleware zincirinden sırayla geçer. Her middleware, isteği bir sonrakine iletmeden önce ve/veya yanıtı geri dönerken işlem yapabilir. Sıra önemlidir çünkü örneğin authentication middleware’i authorization’dan önce, exception handling middleware’i ise genelde zincirin en başına konulmalı, aksi halde sonraki middleware’lerde oluşan hatalar yakalanamaz.
S: appsettings.json ve IOptions pattern’i nasıl çalışır?
C: appsettings.json (ve ortam bazlı appsettings.Development.json gibi dosyalar) uygulama konfigürasyonunu tutar. IOptions<T>, IOptionsSnapshot<T> ve IOptionsMonitor<T> bu ayarları type-safe bir sınıfa bağlamayı sağlar. IOptions<T> singleton olarak yüklenir ve uygulama açıldıktan sonra değişmez, IOptionsSnapshot<T> her request’te yeniden okunur (Scoped), IOptionsMonitor<T> ise değişiklikleri canlı olarak dinleyebilir (Singleton ama değişime duyarlı). Hangi senaryoda hangisinin kullanılacağını bilmek önemli bir detay.
Testing
S: Bir Controller’ı test ederken hangi yaklaşımı kullanırsın?
C: Unit test seviyesinde Controller doğrudan örneklenip bağımlılıkları (servisler) Moq gibi bir kütüphaneyle mock’lanır, sadece Controller’ın kendi mantığı (doğru servis metodunun çağrılması, doğru status code dönülmesi) test edilir. Daha üst seviyede, WebApplicationFactory ile gerçek bir HTTP pipeline’ı ayağa kaldırıp entegrasyon testi yazmak, gerçek middleware ve routing davranışını da doğrular. Senior seviyede ikisi arasındaki farkı ve ne zaman hangisinin gerektiğini bilmek beklenir.
4. Entity Framework Core
EF Core, .NET’in en yaygın ORM’idir ve N+1 problemi, change tracking ve migration yönetimi bu alandaki en kritik konulardır.
S: EF Core’da N+1 problemi nasıl oluşur, nasıl çözülür?
C: Bir liste sorgulanır, sonra her elemanın ilişkili (navigation property) verisi lazy loading ile ayrı ayrı çekilirse N+1 sorgu oluşur. .Include() ile eager loading yaparak ilişkili veriyi tek sorguda (JOIN ile) çekmek en yaygın çözümdür. Çok seviyeli ilişkilerde .ThenInclude() kullanılır. Alternatif olarak .Select() ile sadece ihtiyaç duyulan alanları projekte etmek (projection) hem N+1’i önler hem de gereksiz veri çekimini engeller.
S: AsNoTracking() ne işe yarar, ne zaman kullanılır?
C: EF Core, varsayılan olarak sorgudan dönen her nesneyi change tracker’da izler, böylece SaveChanges() çağrıldığında hangi alanların değiştiğini tespit edebilir. Sadece okuma amaçlı (salt okunur listeleme, raporlama) sorgularda bu izleme gereksiz bellek ve CPU maliyeti yaratır. AsNoTracking() bu izlemeyi kapatır, salt okunur sorgularda performansı belirgin şekilde artırır. Veriyi güncelleyip kaydedecekseniz AsNoTracking() kullanılmamalı, çünkü değişiklikler izlenmeyecektir.
S: Migration’lar nasıl yönetilir, üretim ortamında dikkat edilmesi gereken nokta ne?
C: dotnet ef migrations add ile model değişikliklerinden migration dosyası üretilir, dotnet ef database update ile veritabanına uygulanır. Üretimde uygulamanın kendisinin (context.Database.Migrate() ile) otomatik migration çalıştırması riskli olabilir, özellikle birden fazla instance aynı anda ayağa kalkıyorsa yarış durumu (race condition) oluşabilir. Daha güvenli yaklaşım, migration’ları CI/CD pipeline’ında ayrı ve kontrollü bir adım olarak, uygulama instance’larından bağımsız şekilde çalıştırmaktır.
5. SQL Server
Sorgu optimizasyonu, index kullanımı, isolation level’lar ve transaction yönetimi senior seviyede pratik deneyimle desteklenmesi beklenen konulardır.
S: Yavaş çalışan bir SQL Server sorgusunu nasıl analiz edip optimize edersin?
C: SQL Server Management Studio’da Execution Plan’ı (Actual veya Estimated) incelenir, Index Scan yerine Index Seek olup olmadığına, maliyetin nerede yoğunlaştığına bakılır. sys.dm_exec_query_stats gibi DMV’ler (dynamic management view) sık çalışan ve maliyetli sorguları tespit etmede kullanılabilir. Sık nedenler: eksik veya yanlış sıralı composite index, WHERE koşulunda indexli kolon üzerinde fonksiyon kullanımı, parametre sniffing sorunları, güncel olmayan istatistikler.
S: Isolation level’lar arasındaki fark nedir, hangi senaryoda hangisi tercih edilir?
C: READ UNCOMMITTED en gevşek seviyedir, dirty read’e izin verir, neredeyse hiç kullanılmamalı. READ COMMITTED (SQL Server varsayılanı) dirty read’i engeller ama non-repeatable read ve phantom read’e açıktır. REPEATABLE READ, bir transaction içinde okunan satırların değişmemesini garanti eder ama phantom read hâlâ mümkündür. SERIALIZABLE en katı seviyedir, tam izolasyon sağlar ama en yüksek kilitlenme ve performans maliyetine sahiptir. Çoğu OLTP sisteminde READ COMMITTED (veya SQL Server’a özgü READ COMMITTED SNAPSHOT, versiyonlama tabanlı ve okuma kilitlerini büyük ölçüde azaltan bir seçenek) yeterlidir.
S: Clustered index ile non-clustered index arasındaki fark nedir?
C: Bir tabloda sadece bir tane clustered index olabilir, çünkü tablonun fiziksel veri sırasını belirler (genelde primary key üzerine kurulur). Non-clustered index, ayrı bir yapıdır ve indexlenen kolon değerleriyle birlikte clustered index’e (veya heap’e) bir işaretçi tutar, bu yüzden bir tabloda birden fazla non-clustered index olabilir. Sık filtrelenen ama primary key olmayan kolonlarda non-clustered index eklemek sorgu performansını artırır, ama her ekleme yazma maliyetini de artırır.
S: Stored procedure kullanmak ile LINQ/EF Core üzerinden sorgu yazmak arasında nasıl bir tercih yaparsın?
C: Stored procedure, karmaşık, çok tablolu ve performans kritik sorgularda veritabanı motorunun optimizasyonundan tam olarak faydalanmayı sağlar, ayrıca execution plan cache’lenir. LINQ/EF Core, geliştirme hızı, tip güvenliği ve bakım kolaylığı açısından avantajlıdır, çoğu CRUD senaryosunda yeterlidir. Senior seviyede beklenen cevap, “her zaman biri” demek değil, performans kritik ve karmaşık raporlama sorgularında stored procedure’a, standart iş mantığında EF Core’a yönelmenin makul bir denge olduğunu açıklayabilmek.
6. Mimari ve Kod Kalitesi
S: SOLID prensiplerinden Dependency Inversion’ı .NET’te bir örnekle anlatır mısın?
C: Bir OrderService, doğrudan somut bir SqlOrderRepository sınıfına bağımlı olmak yerine bir IOrderRepository interface’ine bağımlı olmalı. Somut implementasyon, Program.cs içinde builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>() gibi bir satırla DI container’a kaydedilir. Bu sayede veritabanı teknolojisi değişse veya test için sahte (fake/mock) bir implementasyon kullanılsa bile OrderService‘in kendisi hiç değişmez.
S: Clean Architecture’da katmanlar nasıl ayrılır, .NET projelerinde bu genelde nasıl uygulanır?
C: Genelde Domain (iş kuralları, entity’ler, framework’ten tamamen bağımsız), Application (use case’ler, arayüzler), Infrastructure (EF Core, dış servis entegrasyonları, somut implementasyonlar) ve Presentation (API/Controller katmanı) olmak üzere ayrı proje/katmanlara bölünür. Bağımlılık yönü her zaman içe doğrudur: Infrastructure, Domain’e bağımlı olabilir ama Domain hiçbir zaman Infrastructure’a veya EF Core’a bağımlı olmamalıdır. Bu ayrım, iş mantığının veritabanı veya framework değişikliklerinden etkilenmemesini sağlar.
7. Git (Genel Senior Beklentisi)
S: git rebase ile git merge arasındaki fark nedir, hangi durumda hangisini kullanırsın?
C: merge iki branch’in geçmişini birleştirip bir merge commit oluşturur, geçmiş dallanmış şekilde korunur. rebase commit’leri yeni bir base üzerine tekrar oynatır, lineer bir geçmiş yaratır ama commit hash’lerini değiştirir. Paylaşılmış branch’lerde (main, develop) merge tercih edilir. Henüz kimseyle paylaşılmamış lokal feature branch’te, geçmişi sadeleştirmek için rebase kullanılabilir.
8. Davranışsal / Liderlik Soruları
S: Bir üretim (production) kesintisini nasıl yönettiğini anlatır mısın?
C: Bu cevabı kendi somut deneyiminden vermelisin, ama genel çerçeve şöyle kurulabilir: önce etkiyi durdurmaya odaklan (rollback, feature flag kapatma, trafik yönlendirme), paralelde ilgili taraflara durum bildir, kök nedeni sakin şekilde araştır (loglar, metrikler, son deploy’lar), kalıcı çözümü uygula, sonrasında blameless bir post-mortem yaz ve tekrarını önleyecek somut aksiyon çıkar.
Hazırlanman faydalı olacak diğer soru başlıkları: bir teknik kararda ekip arkadaşınla anlaşamadığın bir durum, yanlış bir teknik karar aldığını fark ettiğin bir an, junior bir geliştiriciye mentorluk deneyimin, deadline baskısı altında kod kalitesi konusunda nasıl karar aldığın. Kendi ajansını kurup yönetmiş olman, hem teknik hem yönetimsel bakış açını gösteren özgün bir örnek olarak kullanılabilir.
9. Örnek Canlı Kodlama / Whiteboard Soruları
- Bir LINQ sorgusunu (örneğin gruplama ve toplama içeren bir sorgu) hem method syntax hem query syntax ile yaz.
IEnumerabledöndüren bir metodun deferred execution nedeniyle beklenmedik davrandığı bir kod parçasını debug et.- Thread-safe bir cache sınıfını
ConcurrentDictionarykullanarak tasarla. - Bir sipariş tablosu ve sipariş kalemleri tablosu için N+1 problemi oluşturmayan bir EF Core sorgusu yaz.
- Basit bir rate limiter’ı
SemaphoreSlimkullanarak implemente et.
10. Mülakatta Sen Soracaksın
- Ekip hangi .NET sürümünü kullanıyor, güncelleme (upgrade) süreçleri nasıl yönetiliyor?
- EF Core mü yoksa Dapper gibi micro-ORM’ler mi, yoksa ikisinin karışımı mı kullanılıyor, bu tercih neye göre yapılıyor?
- Kod review süreci ve teknik karar alma mekanizması nasıl işliyor?
- Mikroservis mimarisine mi gidiliyor, yoksa monolith mi korunuyor?
- Senior olarak bu pozisyonda ilk 90 günde başarı nasıl ölçülür?
11. Son Hafta Kontrol Listesi
- [ ] Value type/reference type farkını ve boxing/unboxing maliyetini net anlatabiliyor musun?
- [ ]
async/awaitmekanizmasını veConfigureAwaitkullanımını doğru açıklayabiliyor musun? - [ ]
IEnumerableileIQueryablefarkını ve deferred execution’ı örnekle anlatabiliyor musun? - [ ] Transient/Scoped/Singleton servis ömürlerini ve “captive dependency” hatasını biliyor musun?
- [ ] EF Core’da N+1 problemini ve
AsNoTracking()kullanımını açıklayabiliyor musun? - [ ] SQL Server’da isolation level’ları ve clustered/non-clustered index farkını anlatabiliyor musun?
- [ ] Bir yavaş sorguyu execution plan üzerinden nasıl analiz edeceğini adım adım anlatabiliyor musun?
- [ ] 3-4 STAR formatında davranışsal hikaye hazır mı?
- [ ] Mülakat sonunda soracağın 3-5 soru not edildi mi?