คำถามสัมภาษณ์ C# และ .NET: คู่มือฉบับสมบูรณ์ 2026

คำถามสัมภาษณ์ C# และ .NET ที่พบบ่อยที่สุด 17 ข้อ LINQ, async/await, dependency injection, Entity Framework และ best practice พร้อมคำตอบละเอียดและตัวอย่างโค้ด

คำถามสัมภาษณ์ C# และ .NET - คู่มือฉบับสมบูรณ์

การสัมภาษณ์ C# และ .NET ประเมินความเชี่ยวชาญด้านภาษา ความเข้าใจใน Microsoft ecosystem และความสามารถในการออกแบบแอปพลิเคชันที่แข็งแกร่งและมีประสิทธิภาพสูง คู่มือนี้ครอบคลุมคำถามสำคัญตั้งแต่พื้นฐานภาษาไปจนถึง architectural pattern ขั้นสูง

เคล็ดลับสัมภาษณ์

ผู้สัมภาษณ์ให้คุณค่ากับคำตอบที่แสดงถึงความเข้าใจในกลไกภายในของ .NET ไม่ใช่แค่ syntax เพียงอย่างเดียว การอธิบาย "เหตุผล" เบื้องหลังแต่ละแนวคิดสร้างความแตกต่างอย่างมาก

พื้นฐาน C#

คำถามที่ 1: ความแตกต่างระหว่าง value type กับ reference type คืออะไร?

ความแตกต่างพื้นฐานนี้ส่งผลต่อการจัดสรรหน่วยความจำ ประสิทธิภาพ และพฤติกรรมเมื่อส่งผ่านพารามิเตอร์

ValueVsReference.cscsharp
// 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

ParameterModifiers.cscsharp
// 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 เพื่อเพิ่มประสิทธิภาพการจัดการหน่วยความจำอัตโนมัติ

GarbageCollectorDemo.cscsharp
// 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

EnumerableVsQueryable.cscsharp
// Fundamental execution differences

public class LinqDemo
{
    public static void CompareExecution(AppDbContext context)
    {
        // IEnumerable: executes IN MEMORY (client-side)
        IEnumerable<Product> 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<Product> 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<T> FilterByCondition<T>(
        IEnumerable<T> source,
        Func<T, bool> predicate)
    {
        return source.Where(predicate);
    }

    // Optimized version for IQueryable
    public static IQueryable<T> FilterByCondition<T>(
        IQueryable<T> source,
        Expression<Func<T, bool>> predicate)
    {
        // Expression<Func<>> enables SQL translation
        return source.Where(predicate);
    }
}

ควรใช้ IQueryable กับ Entity Framework เพื่อให้การกรองข้อมูลทำฝั่งฐานข้อมูล IEnumerable เหมาะสำหรับ in-memory collection หรือเมื่อข้อมูลทั้งหมดถูกโหลดเข้ามาแล้ว

คำถามที่ 5: อธิบาย deferred execution ใน LINQ

Deferred execution เป็นแนวคิดพื้นฐานที่ส่งผลต่อประสิทธิภาพและพฤติกรรมของ query

DeferredExecution.cscsharp
// Understanding when queries actually execute

public class DeferredExecutionDemo
{
    public static void Demonstrate()
    {
        var numbers = new List<int> { 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<int> { 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<int> 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 เป็นสิ่งจำเป็นสำหรับแอปพลิเคชันสมัยใหม่ การเข้าใจกลไกภายในแสดงถึงความเชี่ยวชาญขั้นสูง

AsyncAwaitDemo.cscsharp
// Internal mechanisms of asynchronous programming

public class AsyncDemo
{
    // async transforms the method into a state machine
    public async Task<string> 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<Order>)> 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<string> 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<User> GetUserAsync(int id) => Task.FromResult(new User());
    private Task<List<Order>> GetOrdersAsync(int id) => Task.FromResult(new List<Order>());
    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)

DeadlockPrevention.cscsharp
// 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<string> GetDataAsync()
    {
        return await _service.FetchAsync();
    }

    // Solution 2: ConfigureAwait(false) in the library
    public async Task<string> 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<string> 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<string> 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<string> FetchAsync();
}

กฎทอง: "async all the way" หลีกเลี่ยงการผสมโค้ดแบบ synchronous กับ asynchronous ใน ASP.NET Core ไม่มี SynchronizationContext จึงลดความเสี่ยงของ deadlock ได้

พร้อมที่จะพิชิตการสัมภาษณ์ .NET แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Dependency Injection และสถาปัตยกรรม

คำถามที่ 8: อธิบาย DI lifetime แบบต่าง ๆ (Scoped, Transient, Singleton)

การเข้าใจ lifetime มีความสำคัญอย่างยิ่งสำหรับการหลีกเลี่ยงบั๊กด้าน concurrency และ memory leak

DependencyInjectionLifetimes.cscsharp
// 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<SingletonService>();
        services.AddScoped<ScopedService>();
        services.AddTransient<TransientService>();

        // Entity Framework: ALWAYS Scoped
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(connectionString));

        // HttpClient: use IHttpClientFactory
        services.AddHttpClient<IApiClient, ApiClient>();
    }
}

// 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<ScopedService>();
        // Use scoped...
    }
}

กฎ: service ไม่ควร depend กับ service ที่มี lifetime สั้นกว่า Singleton -> Scoped -> Transient

คำถามที่ 9: design pattern หลัก ๆ ใน .NET มีอะไรบ้าง?

ผู้สัมภาษณ์คาดหวังความรู้เชิงปฏิบัติเกี่ยวกับ pattern ไม่ใช่แค่คำนิยาม

DesignPatterns.cscsharp
// Common patterns in C#/.NET

// REPOSITORY: data access abstraction
public interface IUserRepository
{
    Task<User?> GetByIdAsync(int id);
    Task<IEnumerable<User>> 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<User?> GetByIdAsync(int id)
        => await _context.Users.FindAsync(id);

    public async Task<IEnumerable<User>> 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<int> 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<int> 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<User> GetUserAsync(int id);
}

public class UserService : IUserService
{
    private readonly IUserRepository _repository;
    public UserService(IUserRepository repository) => _repository = repository;

    public async Task<User> 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<User> 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

EFCoreOptimization.cscsharp
// Query optimization techniques

public class EFCorePerformance
{
    private readonly AppDbContext _context;

    // N+1 problem: one query per order
    public async Task<List<User>> 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<List<User>> 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<List<UserDto>> 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<List<User>> 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<List<User>> 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<AppDbContext, int, Task<User?>> GetUserById =
        EF.CompileAsyncQuery((AppDbContext ctx, int id) =>
            ctx.Users.FirstOrDefault(u => u.Id == id));

    public async Task<User?> 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

MigrationStrategies.cscsharp
// Professional EF Core migration management

// DbContext configuration with conventions
public class AppDbContext : DbContext
{
    public DbSet<User> Users => Set<User>();
    public DbSet<Order> Orders => Set<Order>();

    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<User>
{
    public void Configure(EntityTypeBuilder<User> 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<Role>().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 การเข้าใจการทำงานมีความจำเป็นอย่างยิ่ง

MiddlewarePipeline.cscsharp
// Request pipeline architecture

// Custom Middleware - full class
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> 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<RequestLoggingMiddleware>();
    }
}

// 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<HttpContext, bool> predicate,
        Action<IApplicationBuilder> 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<ApiRateLimitingMiddleware>()
        );
    }
}

Middleware ทำงานตามลำดับที่ลงทะเบียนเมื่อ request เข้ามา และทำงานในลำดับย้อนกลับเมื่อ response ออกไป

คำถามที่ 13: วิธีจัดทำ JWT authentication

JWT authentication เป็นมาตรฐานสำหรับ REST API สมัยใหม่

JwtAuthentication.cscsharp
// Complete JWT authentication configuration

public static class JwtConfiguration
{
    public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration config)
    {
        var jwtSettings = config.GetSection("Jwt").Get<JwtSettings>()!;

        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<JwtSettings> settings)
    {
        _settings = settings.Value;
    }

    public string GenerateToken(User user, IEnumerable<string> roles)
    {
        var securityKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_settings.SecretKey));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var claims = new List<Claim>
        {
            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<IActionResult> 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!" });
    }
}

พร้อมที่จะพิชิตการสัมภาษณ์ .NET แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

คำถามขั้นสูง

คำถามที่ 14: Span<T> และ Memory<T> คืออะไร?

Type เหล่านี้ช่วยให้จัดการหน่วยความจำได้โดยไม่ต้อง allocate ซึ่งจำเป็นสำหรับโค้ดที่เน้นประสิทธิภาพสูง

SpanAndMemory.cscsharp
// Types for performant memory manipulation

public class HighPerformanceDemo
{
    // Span`<T>`: view over contiguous memory region (stack only)
    public static void SpanBasics()
    {
        // Span over an array
        int[] numbers = { 1, 2, 3, 4, 5 };
        Span<int> span = numbers.AsSpan();

        // Slice without allocation
        Span<int> 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<int> stackSpan = stackalloc int[100];
        stackSpan.Fill(42);
    }

    // Parsing without allocation using Span
    public static bool TryParseDate(ReadOnlySpan<char> 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`<T>`: like Span but can be stored on the heap
    public async Task<int> ProcessDataAsync(Memory<byte> buffer)
    {
        // Memory can cross async boundaries
        await Task.Delay(100);

        // Convert to Span for processing
        Span<byte> 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<byte>.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<byte>.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<char> SubstringWithSpan(ReadOnlySpan<char> input, int start, int length)
    {
        // Returns a view = NO allocation
        return input.Slice(start, length);
    }
}

Span<T> เหมาะอย่างยิ่งสำหรับการประมวลผล string, parsing และการดำเนินการกับ array โดยไม่ต้อง allocate หน่วยความจำ

คำถามที่ 15: อธิบาย record และกรณีการใช้งาน

Record (C# 9+) เป็น immutable reference type ที่มี value-based equality เหมาะสำหรับ DTO และ value object

RecordsDemo.cscsharp
// 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 เป็นสิ่งจำเป็นสำหรับประสิทธิภาพของแอปพลิเคชันขนาดใหญ่

DistributedCaching.cscsharp
// Cache implementation with Redis

public interface ICacheService
{
    Task<T?> GetAsync<T>(string key);
    Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
    Task RemoveAsync(string key);
    Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> 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<T?> GetAsync<T>(string key)
    {
        var data = await _cache.GetStringAsync(key);

        if (string.IsNullOrEmpty(data))
            return default;

        return JsonSerializer.Deserialize<T>(data, _jsonOptions);
    }

    public async Task SetAsync<T>(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<T> GetOrSetAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan? expiration = null)
    {
        var cached = await GetAsync<T>(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<Product?> 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<ICacheService, RedisCacheService>();
    }
}
การ 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 เฉพาะทาง

DistributedTransactions.cscsharp
// 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<OrderResult> 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<T>(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<T> และ Memory<T> สำหรับโค้ดประสิทธิภาพสูง

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

การเตรียมตัวควรผสมผสานทั้งทฤษฎีและการปฏิบัติ การสร้างโปรเจกต์ส่วนตัว การมีส่วนร่วมใน .NET open source ecosystem และการฝึกทำแบบฝึกหัดบนแพลตฟอร์มอย่าง HackerRank หรือ LeetCode จะช่วยเสริมความรู้เหล่านี้สำหรับการสัมภาษณ์ที่มีความท้าทายสูง

แท็ก

#csharp
#dotnet
#interview
#aspnet core
#technical interview

แชร์

บทความที่เกี่ยวข้อง