# คำถามสัมภาษณ์ C# และ .NET: คู่มือฉบับสมบูรณ์ 2026 > คำถามสัมภาษณ์ C# และ .NET ที่พบบ่อยที่สุด 17 ข้อ LINQ, async/await, dependency injection, Entity Framework และ best practice พร้อมคำตอบละเอียดและตัวอย่างโค้ด - Published: 2026-01-25 - Updated: 2026-04-10 - Author: SharpSkill - Tags: csharp, dotnet, interview, aspnet core, technical interview - Reading time: 20 min --- การสัมภาษณ์ C# และ .NET ประเมินความเชี่ยวชาญด้านภาษา ความเข้าใจใน Microsoft ecosystem และความสามารถในการออกแบบแอปพลิเคชันที่แข็งแกร่งและมีประสิทธิภาพสูง คู่มือนี้ครอบคลุมคำถามสำคัญตั้งแต่พื้นฐานภาษาไปจนถึง architectural pattern ขั้นสูง > **เคล็ดลับสัมภาษณ์** > > ผู้สัมภาษณ์ให้คุณค่ากับคำตอบที่แสดงถึงความเข้าใจในกลไกภายในของ .NET ไม่ใช่แค่ syntax เพียงอย่างเดียว การอธิบาย "เหตุผล" เบื้องหลังแต่ละแนวคิดสร้างความแตกต่างอย่างมาก ## พื้นฐาน C# ### คำถามที่ 1: ความแตกต่างระหว่าง value type กับ reference type คืออะไร? ความแตกต่างพื้นฐานนี้ส่งผลต่อการจัดสรรหน่วยความจำ ประสิทธิภาพ และพฤติกรรมเมื่อส่งผ่านพารามิเตอร์ ```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) ถูกจัดสรรบน Stack และถูกปลดปล่อยโดยอัตโนมัติ Reference type (class, interface, delegate) ถูกจัดสรรบน Heap และจัดการโดย Garbage Collector ### คำถามที่ 2: อธิบายคีย์เวิร์ด `ref`, `out` และ `in` ตัวดัดแปลงเหล่านี้ควบคุมวิธีการส่งผ่านพารามิเตอร์ไปยัง method ซึ่งส่งผลต่อประสิทธิภาพและ mutability ```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` มีประโยชน์อย่างยิ่งสำหรับ struct ขนาดใหญ่ เนื่องจากหลีกเลี่ยงการคัดลอกข้อมูลและรับประกัน immutability ในเวลาเดียวกัน นี่คือ pattern ที่พบบ่อยในโค้ดที่เน้นประสิทธิภาพสูง > **ประสิทธิภาพกับ in** > > การใช้ `in` สำหรับ struct ที่มีขนาดมากกว่า 16 byte ช่วยปรับปรุงประสิทธิภาพโดยหลีกเลี่ยงการคัดลอก สำหรับ struct ขนาดเล็ก การส่งผ่านแบบ pass-by-value ยังคงมีประสิทธิภาพมากกว่า ### คำถามที่ 3: Garbage Collector ใน .NET ทำงานอย่างไร? .NET GC ใช้อัลกอริทึมแบบ generational เพื่อเพิ่มประสิทธิภาพการจัดการหน่วยความจำอัตโนมัติ ```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 จะเก็บขยะ Generation 0 บ่อยมาก (ระดับมิลลิวินาที) Generation 1 เป็นครั้งคราว และ Generation 2 ไม่บ่อยนัก วัตถุ LOH (Large Object Heap > 85KB) ถูกจัดการแยกต่างหาก ## LINQ และ Collections ### คำถามที่ 4: ความแตกต่างระหว่าง IEnumerable กับ IQueryable คืออะไร? คำถามนี้มีความสำคัญอย่างยิ่งสำหรับการทำความเข้าใจ deferred execution และประสิทธิภาพของ 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); } } ``` ควรใช้ `IQueryable` กับ Entity Framework เพื่อให้การกรองข้อมูลทำฝั่งฐานข้อมูล `IEnumerable` เหมาะสำหรับ in-memory collection หรือเมื่อข้อมูลทั้งหมดถูกโหลดเข้ามาแล้ว ### คำถามที่ 5: อธิบาย deferred execution ใน LINQ Deferred execution เป็นแนวคิดพื้นฐานที่ส่งผลต่อประสิทธิภาพและพฤติกรรมของ 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; } } ``` > **การ Enumerate ซ้ำ** > > ควรใช้เครื่องมือวิเคราะห์โค้ดอย่าง ReSharper หรือ Rider เพื่อตรวจจับปัญหาการ enumerate ซ้ำที่อาจทำให้เกิดบั๊กที่ยากต่อการค้นหาและปัญหาด้านประสิทธิภาพ ## Async/Await และ Multithreading ### คำถามที่ 6: อธิบาย async/await และการทำงานของ Task การเขียนโปรแกรมแบบ asynchronous เป็นสิ่งจำเป็นสำหรับแอปพลิเคชันสมัยใหม่ การเข้าใจกลไกภายในแสดงถึงความเชี่ยวชาญขั้นสูง ```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 จะแปลง async method เป็น state machine โดยแต่ละ `await` แสดงถึงจุดพักที่ thread ถูกปล่อยคืนสู่ pool ### คำถามที่ 7: วิธีหลีกเลี่ยง deadlock กับ async/await Deadlock จาก async เป็นกับดักคลาสสิก โดยเฉพาะในแอปพลิเคชันที่มี SynchronizationContext (UI, classic ASP.NET) ```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(); } ``` กฎทอง: "async all the way" หลีกเลี่ยงการผสมโค้ดแบบ synchronous กับ asynchronous ใน ASP.NET Core ไม่มี SynchronizationContext จึงลดความเสี่ยงของ deadlock ได้ ## Dependency Injection และสถาปัตยกรรม ### คำถามที่ 8: อธิบาย DI lifetime แบบต่าง ๆ (Scoped, Transient, Singleton) การเข้าใจ lifetime มีความสำคัญอย่างยิ่งสำหรับการหลีกเลี่ยงบั๊กด้าน concurrency และ 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... } } ``` กฎ: service ไม่ควร depend กับ service ที่มี lifetime สั้นกว่า Singleton -> Scoped -> Transient ### คำถามที่ 9: design pattern หลัก ๆ ใน .NET มีอะไรบ้าง? ผู้สัมภาษณ์คาดหวังความรู้เชิงปฏิบัติเกี่ยวกับ pattern ไม่ใช่แค่คำนิยาม ```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 เหล่านี้ถูกใช้ในงานประจำวันของแอปพลิเคชัน .NET ระดับมืออาชีพ Pattern Repository ร่วมกับ Unit of Work พบได้บ่อยเป็นพิเศษเมื่อใช้ Entity Framework ## Entity Framework Core ### คำถามที่ 10: วิธีเพิ่มประสิทธิภาพด้วย EF Core EF Core สามารถทำงานได้เร็วมากหรือช้ามากขึ้นอยู่กับวิธีการใช้งาน คำถามนี้ประเมินความรู้เกี่ยวกับ 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); } } ``` > **การตรวจสอบ Query** > > เปิดใช้งาน SQL logging ใน development ด้วย `optionsBuilder.LogTo(Console.WriteLine)` เพื่อระบุ query ที่มีปัญหา ใน production ควรใช้เครื่องมืออย่าง MiniProfiler หรือ Application Insights ### คำถามที่ 11: อธิบาย migration และการจัดการ schema การจัดการ migration มีความสำคัญอย่างยิ่งสำหรับการ deploy ไปยัง 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" } ); } } ``` คำสั่ง migration ที่สำคัญ: - `dotnet ef migrations add MigrationName` - สร้าง migration - `dotnet ef database update` - apply migration - `dotnet ef migrations script` - สร้าง SQL script - `dotnet ef migrations remove` - ลบ migration ล่าสุด ## ASP.NET Core ### คำถามที่ 12: อธิบาย middleware pipeline ของ ASP.NET Core Middleware pipeline คือหัวใจของ ASP.NET Core การเข้าใจการทำงานมีความจำเป็นอย่างยิ่ง ```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 ทำงานตามลำดับที่ลงทะเบียนเมื่อ request เข้ามา และทำงานในลำดับย้อนกลับเมื่อ response ออกไป ### คำถามที่ 13: วิธีจัดทำ JWT authentication JWT authentication เป็นมาตรฐานสำหรับ REST API สมัยใหม่ ```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!" }); } } ``` ## คำถามขั้นสูง ### คำถามที่ 14: Span`` และ Memory`` คืออะไร? Type เหล่านี้ช่วยให้จัดการหน่วยความจำได้โดยไม่ต้อง allocate ซึ่งจำเป็นสำหรับโค้ดที่เน้นประสิทธิภาพสูง ```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`` เหมาะอย่างยิ่งสำหรับการประมวลผล string, parsing และการดำเนินการกับ array โดยไม่ต้อง allocate หน่วยความจำ ### คำถามที่ 15: อธิบาย record และกรณีการใช้งาน Record (C# 9+) เป็น immutable reference type ที่มี value-based equality เหมาะสำหรับ DTO และ 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 เหมาะสำหรับ: DTO, Value Object, immutable configuration และ object ใด ๆ ที่ identity ขึ้นอยู่กับค่าแทนที่จะเป็น reference ### คำถามที่ 16: วิธีจัดทำระบบ distributed cache Caching เป็นสิ่งจำเป็นสำหรับประสิทธิภาพของแอปพลิเคชันขนาดใหญ่ ```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(); } } ``` > **การ Invalidate Cache** > > "There are only two hard things in Computer Science: cache invalidation and naming things." การกำหนดกลยุทธ์การ invalidate cache ที่ชัดเจนเป็นสิ่งจำเป็นเพื่อหลีกเลี่ยงข้อมูลที่ล้าสมัย ### คำถามที่ 17: วิธีจัดการ distributed transaction ในสถาปัตยกรรม microservices การจัดการ distributed transaction ต้องใช้ pattern เฉพาะทาง ```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); } } ``` SAGA pattern รับประกัน eventual consistency ในระบบกระจาย Outbox pattern ช่วยให้การ publish event มีความน่าเชื่อถือแม้เกิดข้อผิดพลาด ## สรุป การสัมภาษณ์ C# และ .NET ประเมินทั้งความรู้ทางทฤษฎีเกี่ยวกับ runtime และภาษา รวมถึงทักษะเชิงปฏิบัติด้านสถาปัตยกรรมและการพัฒนาแอปพลิเคชัน การเชี่ยวชาญแนวคิดพื้นฐานควบคู่ไปกับความเข้าใจใน pattern ขั้นสูงเป็นสิ่งที่แยกนักพัฒนาระดับ senior ออกจากคนอื่น ### รายการตรวจสอบการเตรียมตัว - เข้าใจความแตกต่างระหว่าง value type กับ reference type - เชี่ยวชาญ async/await และหลีกเลี่ยง deadlock - ทราบความแตกต่างระหว่าง IEnumerable กับ IQueryable - เพิ่มประสิทธิภาพ query ของ Entity Framework Core - จัดทำ IDisposable pattern ได้ถูกต้อง - ตั้งค่า dependency injection ด้วย lifetime ที่เหมาะสม - รักษาความปลอดภัย API ด้วย JWT - ใช้ Span`` และ Memory`` สำหรับโค้ดประสิทธิภาพสูง การเตรียมตัวควรผสมผสานทั้งทฤษฎีและการปฏิบัติ การสร้างโปรเจกต์ส่วนตัว การมีส่วนร่วมใน .NET open source ecosystem และการฝึกทำแบบฝึกหัดบนแพลตฟอร์มอย่าง HackerRank หรือ LeetCode จะช่วยเสริมความรู้เหล่านี้สำหรับการสัมภาษณ์ที่มีความท้าทายสูง --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/dotnet/csharp-dotnet-interview-questions