# Pertanyaan Wawancara C# dan .NET: Panduan Lengkap 2026 > 17 pertanyaan wawancara C# dan .NET yang paling sering diajukan. LINQ, async/await, dependency injection, Entity Framework, dan best practice dengan jawaban mendetail. - Published: 2026-01-25 - Updated: 2026-04-10 - Author: SharpSkill - Tags: csharp, dotnet, interview, aspnet core, technical interview - Reading time: 20 min --- Wawancara C# dan .NET menguji penguasaan bahasa, pemahaman ekosistem Microsoft, serta kemampuan merancang aplikasi yang tangguh dan berperforma tinggi. Panduan ini mencakup pertanyaan-pertanyaan esensial mulai dari dasar-dasar bahasa hingga pola arsitektur tingkat lanjut. > **Tips Wawancara** > > Perekrut menghargai jawaban yang menunjukkan pemahaman mekanisme internal .NET, bukan sekadar sintaksis. Menjelaskan "mengapa" di balik setiap konsep menjadi pembeda yang signifikan. ## Dasar-Dasar C# ### Pertanyaan 1: Apa perbedaan antara value type dan reference type? Perbedaan mendasar ini memengaruhi alokasi memori, performa, dan perilaku saat parameter diteruskan ke method. ```csharp // ValueVsReference.cs // Demonstrating behavior differences // VALUE TYPES: stored on the Stack, copied by value struct Point { public int X; public int Y; } // REFERENCE TYPES: stored on the Heap, copied by reference class Person { public string Name; } public class Demo { public static void Main() { // Value type: independent copy Point p1 = new Point { X = 10, Y = 20 }; Point p2 = p1; // Complete value copy p2.X = 100; // Does NOT modify p1 Console.WriteLine($"p1.X = {p1.X}"); // 10 // Reference type: same object in memory Person person1 = new Person { Name = "Alice" }; Person person2 = person1; // Reference copy person2.Name = "Bob"; // MODIFIES person1 too Console.WriteLine($"person1.Name = {person1.Name}"); // Bob // Special case: string is immutable string s1 = "Hello"; string s2 = s1; s2 = "World"; // Creates a new string Console.WriteLine($"s1 = {s1}"); // Hello } } ``` Value type (int, struct, enum) dialokasikan di Stack dan dibebaskan secara otomatis. Reference type (class, interface, delegate) dialokasikan di Heap dan dikelola oleh Garbage Collector. ### Pertanyaan 2: Jelaskan kata kunci `ref`, `out`, dan `in` Modifier ini mengontrol cara parameter diteruskan ke method, dengan implikasi terhadap performa dan mutabilitas. ```csharp // ParameterModifiers.cs // The three pass-by-reference modifiers public class ParameterDemo { // REF: variable MUST be initialized before the call // Can be read AND modified in the method public static void ModifyWithRef(ref int value) { Console.WriteLine($"Received value: {value}"); value = value * 2; // Modification visible to caller } // OUT: variable does NOT need to be initialized // MUST be assigned before method exits public static bool TryParse(string input, out int result) { // result MUST be assigned in all execution paths if (int.TryParse(input, out result)) { return true; } result = 0; // Required assignment return false; } // IN: read-only pass-by-reference (C# 7.2+) // Avoids copying for large structs without allowing modification public static double CalculateDistance(in Point3D p1, in Point3D p2) { // p1.X = 10; // ERROR: cannot modify 'in' parameter return Math.Sqrt( Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2) + Math.Pow(p2.Z - p1.Z, 2) ); } public static void Main() { // Using ref int number = 5; ModifyWithRef(ref number); Console.WriteLine($"After ref: {number}"); // 10 // Using out if (TryParse("123", out int parsed)) { Console.WriteLine($"Parsed: {parsed}"); // 123 } // Using in (optimal for large structs) var point1 = new Point3D(0, 0, 0); var point2 = new Point3D(3, 4, 0); var distance = CalculateDistance(in point1, in point2); } } public readonly struct Point3D { public readonly double X, Y, Z; public Point3D(double x, double y, double z) => (X, Y, Z) = (x, y, z); } ``` `in` sangat berguna untuk struct besar karena menghindari penyalinan sekaligus menjamin immutabilitas. Pola ini umum digunakan dalam kode berperforma tinggi. > **Performa dengan in** > > Menggunakan `in` untuk struct yang lebih besar dari 16 byte meningkatkan performa dengan menghindari penyalinan. Untuk struct kecil, pass-by-value tetap lebih efisien. ### Pertanyaan 3: Bagaimana cara kerja Garbage Collector di .NET? GC .NET menggunakan algoritma generational untuk mengoptimalkan manajemen memori otomatis. ```csharp // GarbageCollectorDemo.cs // Understanding GC behavior public class GCDemo { public static void DemonstrateGenerations() { // Generation 0: newly allocated objects var shortLived = new byte[1000]; Console.WriteLine($"Generation: {GC.GetGeneration(shortLived)}"); // 0 // Force collection to promote the object GC.Collect(); Console.WriteLine($"After GC: {GC.GetGeneration(shortLived)}"); // 1 GC.Collect(); Console.WriteLine($"After 2nd GC: {GC.GetGeneration(shortLived)}"); // 2 // Memory statistics var info = GC.GetGCMemoryInfo(); Console.WriteLine($"Total heap: {info.HeapSizeBytes / 1024 / 1024}MB"); } // IDisposable pattern for unmanaged resources public class DatabaseConnection : IDisposable { private IntPtr _nativeHandle; private bool _disposed = false; public DatabaseConnection() { _nativeHandle = AllocateNativeResource(); } // Public Dispose method public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); // Prevents finalizer call } // Protected Dispose pattern protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Free managed resources } // Free unmanaged resources if (_nativeHandle != IntPtr.Zero) { FreeNativeResource(_nativeHandle); _nativeHandle = IntPtr.Zero; } _disposed = true; } } // Finalizer (destructor) - called by GC if Dispose wasn't called ~DatabaseConnection() { Dispose(disposing: false); } private IntPtr AllocateNativeResource() => IntPtr.Zero; private void FreeNativeResource(IntPtr handle) { } } } // Recommended usage with using public class Usage { public void Example() { // C# 8+: using declaration using var connection = new GCDemo.DatabaseConnection(); // ... usage // Dispose() called automatically at end of scope } } ``` GC mengumpulkan Generation 0 secara sering (dalam milidetik), Generation 1 sesekali, dan Generation 2 jarang. Objek LOH (Large Object Heap > 85KB) ditangani secara terpisah. ## LINQ dan Koleksi ### Pertanyaan 4: Apa perbedaan antara IEnumerable dan IQueryable? Pertanyaan ini sangat penting untuk memahami deferred execution dan performa query. ```csharp // EnumerableVsQueryable.cs // Fundamental execution differences public class LinqDemo { public static void CompareExecution(AppDbContext context) { // IEnumerable: executes IN MEMORY (client-side) IEnumerable enumerable = context.Products.AsEnumerable(); var filteredEnum = enumerable .Where(p => p.Price > 100) // Filtering in C# .ToList(); // Generated SQL: SELECT * FROM Products (ALL loaded) // IQueryable: executes on DATABASE (server-side) IQueryable queryable = context.Products; var filteredQuery = queryable .Where(p => p.Price > 100) // Translated to SQL WHERE .ToList(); // Generated SQL: SELECT * FROM Products WHERE Price > 100 // Query composition with IQueryable var query = context.Products.AsQueryable(); // Each operation adds to the Expression Tree query = query.Where(p => p.IsActive); query = query.Where(p => p.CategoryId == 5); query = query.OrderBy(p => p.Name); // Execution happens HERE, with a single optimized SQL query var results = query.ToList(); } // Generic method that works with both public static IEnumerable FilterByCondition( IEnumerable source, Func predicate) { return source.Where(predicate); } // Optimized version for IQueryable public static IQueryable FilterByCondition( IQueryable source, Expression> predicate) { // Expression> enables SQL translation return source.Where(predicate); } } ``` Gunakan `IQueryable` bersama Entity Framework agar pemfilteran dilakukan di sisi database. `IEnumerable` cocok untuk koleksi in-memory atau ketika semua data sudah dimuat. ### Pertanyaan 5: Jelaskan deferred execution dalam LINQ Deferred execution merupakan konsep fundamental yang memengaruhi performa dan perilaku query. ```csharp // DeferredExecution.cs // Understanding when queries actually execute public class DeferredExecutionDemo { public static void Demonstrate() { var numbers = new List { 1, 2, 3, 4, 5 }; // Query is DEFINED but NOT EXECUTED var query = numbers.Where(n => { Console.WriteLine($"Evaluating {n}"); return n > 2; }); Console.WriteLine("Query defined, but nothing happened yet"); // Modifying source BEFORE execution numbers.Add(6); numbers.Add(7); Console.WriteLine("Starting iteration:"); // EXECUTION happens HERE during enumeration foreach (var n in query) { Console.WriteLine($"Result: {n}"); } // Output includes 6 and 7 because they were added before execution } // Methods that FORCE immediate execution public static void ImmediateExecution() { var numbers = new List { 1, 2, 3, 4, 5 }; // ToList(), ToArray(), ToDictionary() = immediate execution var list = numbers.Where(n => n > 2).ToList(); // Count(), First(), Single(), Any() = immediate execution var count = numbers.Where(n => n > 2).Count(); var first = numbers.First(n => n > 2); // Aggregate(), Sum(), Max(), Min() = immediate execution var sum = numbers.Where(n => n > 2).Sum(); } // Danger: multiple enumeration public static void MultipleEnumerationProblem() { var numbers = GetNumbers(); // IEnumerable returned by yield // WARNING: EACH use re-executes the query var count = numbers.Count(); // 1st enumeration var first = numbers.First(); // 2nd enumeration // SOLUTION: materialize once var materializedList = numbers.ToList(); var countOk = materializedList.Count; // No re-execution var firstOk = materializedList.First(); // No re-execution } private static IEnumerable GetNumbers() { Console.WriteLine("GetNumbers called"); yield return 1; yield return 2; yield return 3; } } ``` > **Enumerasi Ganda** > > Gunakan analyzer seperti ReSharper atau Rider untuk mendeteksi masalah multiple enumeration yang dapat menyebabkan bug tersembunyi dan masalah performa. ## Async/Await dan Multithreading ### Pertanyaan 6: Jelaskan async/await dan cara kerja Task Pemrograman asynchronous sangat penting untuk aplikasi modern. Memahami mekanisme internalnya menunjukkan keahlian tingkat lanjut. ```csharp // AsyncAwaitDemo.cs // Internal mechanisms of asynchronous programming public class AsyncDemo { // async transforms the method into a state machine public async Task FetchDataAsync(string url) { using var client = new HttpClient(); // await releases the thread during I/O wait // Thread returns to pool and can process other requests var response = await client.GetStringAsync(url); // After await, execution resumes (possibly on different thread) return ProcessData(response); } // Pattern for parallel execution public async Task<(User, List)> GetUserWithOrdersAsync(int userId) { // Both calls start SIMULTANEOUSLY var userTask = GetUserAsync(userId); var ordersTask = GetOrdersAsync(userId); // await waits for both results await Task.WhenAll(userTask, ordersTask); return (userTask.Result, ordersTask.Result); } // ConfigureAwait for libraries public async Task LibraryMethodAsync() { // ConfigureAwait(false) avoids capturing SynchronizationContext // Recommended in libraries to avoid deadlocks var data = await FetchDataAsync("https://api.example.com") .ConfigureAwait(false); return data.ToUpper(); } // Anti-pattern: async void (except for event handlers) public async void BadAsyncMethod() { // Exceptions cannot be caught // Impossible to await completion await Task.Delay(100); } // Correct: async Task public async Task GoodAsyncMethod() { await Task.Delay(100); } private Task GetUserAsync(int id) => Task.FromResult(new User()); private Task> GetOrdersAsync(int id) => Task.FromResult(new List()); private string ProcessData(string data) => data; } public class User { } public class Order { } ``` Compiler mengubah method async menjadi state machine. Setiap `await` merepresentasikan titik suspensi di mana thread dilepaskan. ### Pertanyaan 7: Bagaimana cara menghindari deadlock dengan async/await? Deadlock async merupakan jebakan klasik, terutama pada aplikasi dengan SynchronizationContext (UI, ASP.NET klasik). ```csharp // DeadlockPrevention.cs // Patterns to avoid deadlocks public class DeadlockDemo { private readonly IDataService _service; // DEADLOCK in classic ASP.NET or WinForms/WPF public string GetDataDeadlock() { // .Result or .Wait() blocks the UI/Request thread // async tries to resume on that same thread = deadlock return _service.FetchAsync().Result; } // Solution 1: async all the way public async Task GetDataAsync() { return await _service.FetchAsync(); } // Solution 2: ConfigureAwait(false) in the library public async Task FetchAsync() { var data = await HttpClient.GetStringAsync("url") .ConfigureAwait(false); // Don't capture context return data; } // Solution 3: Task.Run to isolate (if really necessary) public string GetDataWithTaskRun() { // Runs on thread pool without SynchronizationContext return Task.Run(async () => await _service.FetchAsync()).Result; } // Pattern for proper cancellation public async Task FetchWithCancellation(CancellationToken cancellationToken) { using var client = new HttpClient(); try { var response = await client.GetStringAsync("url", cancellationToken); return response; } catch (OperationCanceledException) { // Handle cancellation gracefully return string.Empty; } } // Timeout pattern public async Task FetchWithTimeout(TimeSpan timeout) { using var cts = new CancellationTokenSource(timeout); try { return await FetchWithCancellation(cts.Token); } catch (OperationCanceledException) { throw new TimeoutException("Request timed out"); } } private static readonly HttpClient HttpClient = new(); } public interface IDataService { Task FetchAsync(); } ``` Aturan utamanya: "async all the way". Hindari mencampur kode synchronous dan asynchronous. Di ASP.NET Core, SynchronizationContext tidak ada, sehingga risiko deadlock berkurang. ## Dependency Injection dan Arsitektur ### Pertanyaan 8: Jelaskan perbedaan lifetime DI (Scoped, Transient, Singleton) Memahami lifetime sangat penting untuk menghindari bug concurrency dan memory leak. ```csharp // DependencyInjectionLifetimes.cs // The three lifetimes and their implications // SINGLETON: single instance for the entire application public class SingletonService { private readonly Guid _id = Guid.NewGuid(); public Guid Id => _id; // DANGER: no mutable state without synchronization // private int _counter; // Possible race conditions } // SCOPED: one instance per HTTP request (or scope) public class ScopedService { private readonly Guid _id = Guid.NewGuid(); public Guid Id => _id; // Safe: each request has its own instance // Ideal for DbContext, UnitOfWork } // TRANSIENT: new instance on every injection public class TransientService { private readonly Guid _id = Guid.NewGuid(); public Guid Id => _id; // Ideal for lightweight, stateless services } // Configuration in Program.cs public static class ServiceConfiguration { public static void ConfigureServices(IServiceCollection services) { services.AddSingleton(); services.AddScoped(); services.AddTransient(); // Entity Framework: ALWAYS Scoped services.AddDbContext(options => options.UseSqlServer(connectionString)); // HttpClient: use IHttpClientFactory services.AddHttpClient(); } } // CAPTIVE DEPENDENCY: Singleton depending on Scoped public class BadSingletonService { // WARNING: ScopedService will be captured and reused indefinitely // Causes concurrency bugs and stale data private readonly ScopedService _scoped; public BadSingletonService(ScopedService scoped) { _scoped = scoped; } } // SOLUTION: use IServiceScopeFactory public class GoodSingletonService { private readonly IServiceScopeFactory _scopeFactory; public GoodSingletonService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public async Task DoWork() { // Create explicit scope to get fresh ScopedService using var scope = _scopeFactory.CreateScope(); var scoped = scope.ServiceProvider.GetRequiredService(); // Use scoped... } } ``` Aturannya: sebuah service tidak boleh bergantung pada service dengan lifetime yang lebih pendek. Singleton -> Scoped -> Transient. ### Pertanyaan 9: Apa saja design pattern utama di .NET? Perekrut mengharapkan pengetahuan praktis tentang pattern, bukan sekadar definisi. ```csharp // DesignPatterns.cs // Common patterns in C#/.NET // REPOSITORY: data access abstraction public interface IUserRepository { Task GetByIdAsync(int id); Task> GetAllAsync(); Task AddAsync(User user); Task UpdateAsync(User user); Task DeleteAsync(int id); } public class UserRepository : IUserRepository { private readonly AppDbContext _context; public UserRepository(AppDbContext context) => _context = context; public async Task GetByIdAsync(int id) => await _context.Users.FindAsync(id); public async Task> GetAllAsync() => await _context.Users.ToListAsync(); public async Task AddAsync(User user) => await _context.Users.AddAsync(user); public async Task UpdateAsync(User user) => _context.Users.Update(user); public async Task DeleteAsync(int id) { var user = await GetByIdAsync(id); if (user != null) _context.Users.Remove(user); } } // UNIT OF WORK: transaction coordination public interface IUnitOfWork : IDisposable { IUserRepository Users { get; } IOrderRepository Orders { get; } Task SaveChangesAsync(); } public class UnitOfWork : IUnitOfWork { private readonly AppDbContext _context; public UnitOfWork(AppDbContext context) { _context = context; Users = new UserRepository(context); Orders = new OrderRepository(context); } public IUserRepository Users { get; } public IOrderRepository Orders { get; } public async Task SaveChangesAsync() => await _context.SaveChangesAsync(); public void Dispose() => _context.Dispose(); } // FACTORY: complex object creation public interface INotificationFactory { INotification Create(NotificationType type); } public class NotificationFactory : INotificationFactory { public INotification Create(NotificationType type) => type switch { NotificationType.Email => new EmailNotification(), NotificationType.Sms => new SmsNotification(), NotificationType.Push => new PushNotification(), _ => throw new ArgumentException($"Unknown type: {type}") }; } // DECORATOR: adding behaviors dynamically public interface IUserService { Task GetUserAsync(int id); } public class UserService : IUserService { private readonly IUserRepository _repository; public UserService(IUserRepository repository) => _repository = repository; public async Task GetUserAsync(int id) => await _repository.GetByIdAsync(id) ?? throw new NotFoundException($"User {id} not found"); } // Decorator that adds caching public class CachedUserService : IUserService { private readonly IUserService _inner; private readonly IMemoryCache _cache; public CachedUserService(IUserService inner, IMemoryCache cache) { _inner = inner; _cache = cache; } public async Task GetUserAsync(int id) { var cacheKey = $"user:{id}"; if (_cache.TryGetValue(cacheKey, out User? cached)) return cached!; var user = await _inner.GetUserAsync(id); _cache.Set(cacheKey, user, TimeSpan.FromMinutes(5)); return user; } } ``` Pattern-pattern ini digunakan setiap hari dalam aplikasi .NET profesional. Pattern Repository dengan Unit of Work sangat umum dipakai bersama Entity Framework. ## Entity Framework Core ### Pertanyaan 10: Bagaimana cara mengoptimalkan performa dengan EF Core? EF Core bisa sangat cepat atau sangat lambat tergantung cara penggunaannya. Pertanyaan ini mengevaluasi pengetahuan tentang best practice. ```csharp // EFCoreOptimization.cs // Query optimization techniques public class EFCorePerformance { private readonly AppDbContext _context; // N+1 problem: one query per order public async Task> GetUsersWithOrdersBad() { var users = await _context.Users.ToListAsync(); foreach (var user in users) { // N additional queries! var orders = await _context.Orders .Where(o => o.UserId == user.Id) .ToListAsync(); } return users; } // Eager Loading with Include public async Task> GetUsersWithOrdersGood() { return await _context.Users .Include(u => u.Orders) // SQL JOIN .ThenInclude(o => o.Products) // Nested include .ToListAsync(); } // Projection to load only necessary data public async Task> GetUserSummaries() { return await _context.Users .Select(u => new UserDto { Id = u.Id, Name = u.Name, OrderCount = u.Orders.Count, // Calculated SQL-side TotalSpent = u.Orders.Sum(o => o.Total) }) .ToListAsync(); } // Split Query for large collections public async Task> GetUsersWithSplitQuery() { return await _context.Users .Include(u => u.Orders) .AsSplitQuery() // Generates separate queries instead of large JOIN .ToListAsync(); } // No Tracking for read-only operations public async Task> GetUsersReadOnly() { return await _context.Users .AsNoTracking() // No change tracking = faster .ToListAsync(); } // Batch operations (EF Core 7+) public async Task DeleteInactiveUsers() { // Single DELETE query instead of load then delete await _context.Users .Where(u => !u.IsActive && u.LastLoginAt < DateTime.UtcNow.AddYears(-1)) .ExecuteDeleteAsync(); } // Bulk update public async Task DeactivateOldUsers() { await _context.Users .Where(u => u.LastLoginAt < DateTime.UtcNow.AddMonths(-6)) .ExecuteUpdateAsync(u => u.SetProperty(x => x.IsActive, false)); } // Compiled Queries for frequent queries private static readonly Func> GetUserById = EF.CompileAsyncQuery((AppDbContext ctx, int id) => ctx.Users.FirstOrDefault(u => u.Id == id)); public async Task GetUserOptimized(int id) { return await GetUserById(_context, id); } } ``` > **Monitoring Query** > > Aktifkan SQL logging di development dengan `optionsBuilder.LogTo(Console.WriteLine)` untuk mengidentifikasi query bermasalah. Di production, gunakan tools seperti MiniProfiler atau Application Insights. ### Pertanyaan 11: Jelaskan migrasi dan manajemen skema Manajemen migrasi sangat penting untuk deployment di production. ```csharp // MigrationStrategies.cs // Professional EF Core migration management // DbContext configuration with conventions public class AppDbContext : DbContext { public DbSet Users => Set(); public DbSet Orders => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { // Apply all IEntityTypeConfiguration configurations modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly); // Global convention for dates foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { foreach (var property in entityType.GetProperties()) { if (property.ClrType == typeof(DateTime)) { property.SetColumnType("datetime2"); } } } } } // Separate fluent configuration public class UserConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Users"); builder.HasKey(u => u.Id); builder.Property(u => u.Email) .IsRequired() .HasMaxLength(256); builder.HasIndex(u => u.Email) .IsUnique(); builder.HasMany(u => u.Orders) .WithOne(o => o.User) .HasForeignKey(o => o.UserId) .OnDelete(DeleteBehavior.Cascade); } } // Data seeding public class DataSeeder { public static void Seed(ModelBuilder modelBuilder) { modelBuilder.Entity().HasData( new Role { Id = 1, Name = "Admin" }, new Role { Id = 2, Name = "User" } ); } } ``` Perintah migrasi yang penting: - `dotnet ef migrations add MigrationName` - Membuat migrasi - `dotnet ef database update` - Menerapkan migrasi - `dotnet ef migrations script` - Menghasilkan script SQL - `dotnet ef migrations remove` - Menghapus migrasi terakhir ## ASP.NET Core ### Pertanyaan 12: Jelaskan middleware pipeline di ASP.NET Core Middleware pipeline merupakan inti dari ASP.NET Core. Memahami cara kerjanya sangat esensial. ```csharp // MiddlewarePipeline.cs // Request pipeline architecture // Custom Middleware - full class public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public RequestLoggingMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { // BEFORE: executed on the way in (request) var stopwatch = Stopwatch.StartNew(); _logger.LogInformation("Request: {Method} {Path}", context.Request.Method, context.Request.Path); try { // Pass to next middleware await _next(context); } finally { // AFTER: executed on the way out (response) stopwatch.Stop(); _logger.LogInformation("Response: {StatusCode} in {ElapsedMs}ms", context.Response.StatusCode, stopwatch.ElapsedMilliseconds); } } } // Extension for registration public static class MiddlewareExtensions { public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder app) { return app.UseMiddleware(); } } // Pipeline configuration in Program.cs public class Startup { public void Configure(IApplicationBuilder app) { // ORDER is CRITICAL! // 1. Exception handling (must be first) app.UseExceptionHandler("/error"); // 2. HTTPS Redirection app.UseHttpsRedirection(); // 3. Static files (short-circuits if found) app.UseStaticFiles(); // 4. Routing (determines endpoint) app.UseRouting(); // 5. CORS (must be between Routing and Auth) app.UseCors(); // 6. Authentication (who are you?) app.UseAuthentication(); // 7. Authorization (are you allowed?) app.UseAuthorization(); // 8. Custom middleware app.UseRequestLogging(); // 9. Endpoints (executes controller/action) app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); }); } } // Conditional middleware public static class ConditionalMiddleware { public static IApplicationBuilder UseWhen( this IApplicationBuilder app, Func predicate, Action configuration) { // Conditional branch of the pipeline return app.UseWhen(predicate, configuration); } public static void Example(IApplicationBuilder app) { // Apply middleware only for /api/* app.UseWhen( context => context.Request.Path.StartsWithSegments("/api"), apiApp => apiApp.UseMiddleware() ); } } ``` Middleware dieksekusi sesuai urutan registrasi saat masuk (request) dan dalam urutan terbalik saat keluar (response). ### Pertanyaan 13: Bagaimana cara mengimplementasikan autentikasi JWT? Autentikasi JWT merupakan standar untuk REST API modern. ```csharp // JwtAuthentication.cs // Complete JWT authentication configuration public static class JwtConfiguration { public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration config) { var jwtSettings = config.GetSection("Jwt").Get()!; services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSettings.Issuer, ValidAudience = jwtSettings.Audience, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(jwtSettings.SecretKey)), ClockSkew = TimeSpan.Zero // No tolerance on expiration }; // Events for logging/debugging options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { if (context.Exception is SecurityTokenExpiredException) { context.Response.Headers.Add("Token-Expired", "true"); } return Task.CompletedTask; } }; }); } } public class JwtSettings { public string SecretKey { get; set; } = string.Empty; public string Issuer { get; set; } = string.Empty; public string Audience { get; set; } = string.Empty; public int ExpirationMinutes { get; set; } = 60; } // Token generation service public class TokenService { private readonly JwtSettings _settings; public TokenService(IOptions settings) { _settings = settings.Value; } public string GenerateToken(User user, IEnumerable roles) { var securityKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(_settings.SecretKey)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new List { new(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new(JwtRegisteredClaimNames.Email, user.Email), new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new("name", user.Name) }; // Add roles as claims claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); var token = new JwtSecurityToken( issuer: _settings.Issuer, audience: _settings.Audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(_settings.ExpirationMinutes), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); } public ClaimsPrincipal? ValidateToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(_settings.SecretKey); try { var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = true, ValidIssuer = _settings.Issuer, ValidateAudience = true, ValidAudience = _settings.Audience, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }, out _); return principal; } catch { return null; } } } // Usage in a controller [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly TokenService _tokenService; private readonly IUserService _userService; [HttpPost("login")] public async Task Login([FromBody] LoginDto dto) { var user = await _userService.ValidateCredentialsAsync(dto.Email, dto.Password); if (user == null) return Unauthorized(new { message = "Invalid credentials" }); var roles = await _userService.GetRolesAsync(user.Id); var token = _tokenService.GenerateToken(user, roles); return Ok(new { token, expiresIn = 3600 }); } [Authorize] // Requires valid token [HttpGet("profile")] public IActionResult GetProfile() { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; return Ok(new { userId }); } [Authorize(Roles = "Admin")] // Requires Admin role [HttpGet("admin")] public IActionResult AdminOnly() { return Ok(new { message = "Welcome, Admin!" }); } } ``` ## Pertanyaan Lanjutan ### Pertanyaan 14: Apa itu Span`` dan Memory``? Tipe-tipe ini memungkinkan manipulasi memori tanpa alokasi, esensial untuk kode berperforma tinggi. ```csharp // SpanAndMemory.cs // Types for performant memory manipulation public class HighPerformanceDemo { // Span``: view over contiguous memory region (stack only) public static void SpanBasics() { // Span over an array int[] numbers = { 1, 2, 3, 4, 5 }; Span span = numbers.AsSpan(); // Slice without allocation Span slice = span.Slice(1, 3); // [2, 3, 4] // Modification affects original array slice[0] = 100; Console.WriteLine(numbers[1]); // 100 // Span on the stack (stackalloc) Span stackSpan = stackalloc int[100]; stackSpan.Fill(42); } // Parsing without allocation using Span public static bool TryParseDate(ReadOnlySpan input, out DateTime date) { // Format: "2024-01-15" date = default; if (input.Length != 10) return false; // Slicing without creating new strings var yearSpan = input.Slice(0, 4); var monthSpan = input.Slice(5, 2); var daySpan = input.Slice(8, 2); if (!int.TryParse(yearSpan, out int year)) return false; if (!int.TryParse(monthSpan, out int month)) return false; if (!int.TryParse(daySpan, out int day)) return false; date = new DateTime(year, month, day); return true; } // Memory``: like Span but can be stored on the heap public async Task ProcessDataAsync(Memory buffer) { // Memory can cross async boundaries await Task.Delay(100); // Convert to Span for processing Span span = buffer.Span; int sum = 0; foreach (var b in span) { sum += b; } return sum; } // ArrayPool: array reuse to avoid allocations public static void UseArrayPool() { // Rent an array from the pool byte[] buffer = ArrayPool.Shared.Rent(1024); try { // Use the buffer... // Note: may be larger than requested Console.WriteLine($"Buffer size: {buffer.Length}"); } finally { // ALWAYS return to pool ArrayPool.Shared.Return(buffer, clearArray: true); } } // Comparative benchmark public static string SubstringTraditional(string input, int start, int length) { // Creates new string = allocation return input.Substring(start, length); } public static ReadOnlySpan SubstringWithSpan(ReadOnlySpan input, int start, int length) { // Returns a view = NO allocation return input.Slice(start, length); } } ``` Span`` ideal untuk pemrosesan string, parsing, dan operasi array tanpa alokasi. ### Pertanyaan 15: Jelaskan record dan kasus penggunaannya Record (C# 9+) adalah reference type yang immutable dengan equality berbasis nilai, cocok untuk DTO dan value object. ```csharp // RecordsDemo.cs // Features and use cases for records // Record class (reference, immutable by default) public record Person(string FirstName, string LastName, DateOnly BirthDate) { // Computed property public int Age => DateTime.Today.Year - BirthDate.Year; // Additional method public string FullName => $"{FirstName} {LastName}"; } // Record with validation public record Email { public string Value { get; } public Email(string value) { if (!IsValidEmail(value)) throw new ArgumentException("Invalid email format"); Value = value; } private static bool IsValidEmail(string email) => !string.IsNullOrEmpty(email) && email.Contains('@'); } // Record struct (value, C# 10+) public readonly record struct Point(double X, double Y) { public double Distance => Math.Sqrt(X * X + Y * Y); } public class RecordUsageDemo { public void DemonstrateFeatures() { // Creation var person1 = new Person("John", "Doe", new DateOnly(1990, 5, 15)); // Value-based equality (not reference) var person2 = new Person("John", "Doe", new DateOnly(1990, 5, 15)); Console.WriteLine(person1 == person2); // True // Mutation with 'with' (creates a copy) var person3 = person1 with { LastName = "Smith" }; Console.WriteLine(person1.LastName); // "Doe" (unchanged) Console.WriteLine(person3.LastName); // "Smith" // Deconstruction var (firstName, lastName, _) = person1; Console.WriteLine($"{firstName} {lastName}"); // Auto-generated ToString() Console.WriteLine(person1); // Output: Person { FirstName = John, LastName = Doe, BirthDate = 15/05/1990 } } // Records as DTOs (data transfer) public record CreateUserRequest(string Email, string Password, string Name); public record UserResponse(int Id, string Email, string Name, DateTime CreatedAt); // Records as Value Objects (DDD) public record Money(decimal Amount, string Currency) { public static Money operator +(Money a, Money b) { if (a.Currency != b.Currency) throw new InvalidOperationException("Currency mismatch"); return new Money(a.Amount + b.Amount, a.Currency); } } // Record with inheritance public abstract record Shape(string Color); public record Circle(string Color, double Radius) : Shape(Color); public record Rectangle(string Color, double Width, double Height) : Shape(Color); } ``` Record ideal untuk: DTO, Value Object, konfigurasi immutable, dan objek apa pun yang identitasnya didasarkan pada nilai, bukan referensi. ### Pertanyaan 16: Bagaimana cara mengimplementasikan sistem distributed cache? Caching sangat penting untuk performa aplikasi berskala besar. ```csharp // DistributedCaching.cs // Cache implementation with Redis public interface ICacheService { Task GetAsync(string key); Task SetAsync(string key, T value, TimeSpan? expiration = null); Task RemoveAsync(string key); Task GetOrSetAsync(string key, Func> factory, TimeSpan? expiration = null); } public class RedisCacheService : ICacheService { private readonly IDistributedCache _cache; private readonly JsonSerializerOptions _jsonOptions; public RedisCacheService(IDistributedCache cache) { _cache = cache; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; } public async Task GetAsync(string key) { var data = await _cache.GetStringAsync(key); if (string.IsNullOrEmpty(data)) return default; return JsonSerializer.Deserialize(data, _jsonOptions); } public async Task SetAsync(string key, T value, TimeSpan? expiration = null) { var options = new DistributedCacheEntryOptions(); if (expiration.HasValue) { options.AbsoluteExpirationRelativeToNow = expiration; } else { options.SlidingExpiration = TimeSpan.FromMinutes(10); } var json = JsonSerializer.Serialize(value, _jsonOptions); await _cache.SetStringAsync(key, json, options); } public async Task RemoveAsync(string key) { await _cache.RemoveAsync(key); } // Cache-Aside pattern with factory public async Task GetOrSetAsync( string key, Func> factory, TimeSpan? expiration = null) { var cached = await GetAsync(key); if (cached != null) return cached; var value = await factory(); await SetAsync(key, value, expiration); return value; } } // Usage in a service public class ProductService { private readonly ICacheService _cache; private readonly IProductRepository _repository; public ProductService(ICacheService cache, IProductRepository repository) { _cache = cache; _repository = repository; } public async Task GetProductAsync(int id) { var cacheKey = $"product:{id}"; return await _cache.GetOrSetAsync( cacheKey, async () => await _repository.GetByIdAsync(id), TimeSpan.FromMinutes(30) ); } // Cache invalidation public async Task UpdateProductAsync(int id, UpdateProductDto dto) { await _repository.UpdateAsync(id, dto); // Invalidate cache await _cache.RemoveAsync($"product:{id}"); } } // Configuration in Program.cs public static class CacheConfiguration { public static void AddCaching(this IServiceCollection services, IConfiguration config) { services.AddStackExchangeRedisCache(options => { options.Configuration = config.GetConnectionString("Redis"); options.InstanceName = "MyApp:"; }); services.AddSingleton(); } } ``` > **Invalidasi Cache** > > "There are only two hard things in Computer Science: cache invalidation and naming things." Mendefinisikan strategi invalidasi cache yang jelas sangat penting untuk menghindari data yang sudah usang. ### Pertanyaan 17: Bagaimana cara menangani transaksi terdistribusi? Dalam arsitektur microservice, transaksi terdistribusi memerlukan pattern-pattern khusus. ```csharp // DistributedTransactions.cs // Patterns for consistency in distributed systems // SAGA Pattern with Orchestration public class OrderSaga { private readonly IOrderRepository _orderRepository; private readonly IPaymentService _paymentService; private readonly IInventoryService _inventoryService; private readonly INotificationService _notificationService; public async Task ProcessOrderAsync(CreateOrderCommand command) { Order? order = null; PaymentResult? payment = null; InventoryReservation? reservation = null; try { // Step 1: Create order order = await _orderRepository.CreateAsync(command); // Step 2: Reserve inventory reservation = await _inventoryService.ReserveAsync(order.Items); // Step 3: Process payment payment = await _paymentService.ProcessAsync(order.Total, command.PaymentMethod); // Step 4: Confirm order await _orderRepository.ConfirmAsync(order.Id); // Step 5: Notification (non-critical) await _notificationService.SendOrderConfirmationAsync(order); return OrderResult.Success(order.Id); } catch (Exception ex) { // COMPENSATION: undo previous steps in reverse order if (payment?.IsSuccessful == true) { await _paymentService.RefundAsync(payment.TransactionId); } if (reservation != null) { await _inventoryService.ReleaseReservationAsync(reservation.Id); } if (order != null) { await _orderRepository.CancelAsync(order.Id, ex.Message); } return OrderResult.Failure(ex.Message); } } } // Outbox Pattern for reliable event publishing public class OutboxProcessor { private readonly AppDbContext _context; private readonly IMessageBus _messageBus; public async Task ProcessOutboxAsync() { var pendingMessages = await _context.OutboxMessages .Where(m => m.ProcessedAt == null) .OrderBy(m => m.CreatedAt) .Take(100) .ToListAsync(); foreach (var message in pendingMessages) { try { // Publish message await _messageBus.PublishAsync(message.Type, message.Payload); // Mark as processed message.ProcessedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); } catch (Exception ex) { message.RetryCount++; message.Error = ex.Message; await _context.SaveChangesAsync(); } } } } // Outbox model public class OutboxMessage { public Guid Id { get; set; } public string Type { get; set; } = string.Empty; public string Payload { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } public DateTime? ProcessedAt { get; set; } public int RetryCount { get; set; } public string? Error { get; set; } } // Extension to add outbox message within a transaction public static class DbContextExtensions { public static void AddOutboxMessage(this AppDbContext context, T @event) { var message = new OutboxMessage { Id = Guid.NewGuid(), Type = typeof(T).Name, Payload = JsonSerializer.Serialize(@event), CreatedAt = DateTime.UtcNow }; context.OutboxMessages.Add(message); } } ``` Pola SAGA menjamin eventual consistency dalam sistem terdistribusi. Pola Outbox memastikan penerbitan event yang andal bahkan saat terjadi kegagalan. ## Kesimpulan Wawancara C# dan .NET mengevaluasi kombinasi pengetahuan teoretis tentang runtime dan bahasa, serta keterampilan praktis dalam arsitektur dan pengembangan aplikasi. Menguasai konsep-konsep fundamental sambil memahami pattern tingkat lanjut menjadi pembeda antara developer senior dan junior. ### Checklist Persiapan - Memahami perbedaan antara value type dan reference type - Menguasai async/await dan menghindari deadlock - Mengetahui perbedaan antara IEnumerable dan IQueryable - Mengoptimalkan query Entity Framework Core - Mengimplementasikan pola IDisposable dengan benar - Mengkonfigurasi dependency injection dengan lifetime yang tepat - Mengamankan API dengan JWT - Menggunakan Span`` dan Memory`` untuk kode berperforma tinggi Persiapan sebaiknya menggabungkan teori dan praktik. Membangun proyek pribadi, berkontribusi pada ekosistem open source .NET, dan menyelesaikan latihan di platform seperti HackerRank atau LeetCode akan mengonsolidasikan pengetahuan ini untuk menghadapi wawancara yang paling menuntut. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/dotnet/csharp-dotnet-interview-questions