C#/.NET面接質問集:2026年完全ガイド
C#と.NETの技術面接で頻出の17問を徹底解説。LINQ、async/await、依存性注入、Entity Framework Coreからアーキテクチャパターンまで、コード例付きで実践的に学べます。

C#と.NETの技術面接では、言語の深い理解、Microsoftエコシステムの知識、堅牢で高性能なアプリケーションを設計する能力が問われます。本ガイドでは、言語の基礎から高度なアーキテクチャパターンまで、実務で必須となる17の質問を取り上げます。
面接官が評価するのは、構文の暗記ではなく.NETの内部メカニズムに対する理解です。各概念について「なぜそうなるのか」を説明できることが合否を分けます。
C#の基礎
質問1:値型と参照型の違いは何ですか?
この基本的な区別は、メモリ割り当て、パフォーマンス、パラメータ渡し時の動作に直接影響します。
// 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
}
}値型(int、struct、enum)はスタックに割り当てられ、自動的に解放されます。参照型(class、interface、delegate)はヒープに割り当てられ、ガベージコレクタによって管理されます。
質問2:ref、out、inキーワードの違いを説明してください
これらの修飾子は、メソッドへのパラメータの渡し方を制御し、パフォーマンスと可変性に影響を与えます。
// 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は大きな構造体に対して特に有効で、コピーを回避しつつ不変性を保証します。高性能コードでは一般的なパターンです。
16バイトを超える構造体にinを使用すると、コピーが回避されパフォーマンスが向上します。小さな構造体の場合は、値渡しの方が効率的です。
質問3:.NETのガベージコレクタはどのように動作しますか?
.NETのGCは、世代別アルゴリズムを使用して自動メモリ管理を最適化します。
// 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とコレクション
質問4:IEnumerableとIQueryableの違いは何ですか?
この質問は、遅延実行とクエリパフォーマンスの理解を確認する上で極めて重要です。
// 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);
}
}Entity Frameworkを使用する場合はIQueryableを使い、フィルタリングをデータベース側で実行させます。IEnumerableは、インメモリコレクションやデータが既に読み込まれている場合に適しています。
質問5:LINQの遅延実行について説明してください
遅延実行は、パフォーマンスとクエリの動作に影響を与える基本的な概念です。
// 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;
}
}ReSharperやRiderなどのアナライザーを使用して、パフォーマンス低下や微妙なバグの原因となる複数回列挙の問題を検出することを推奨します。
Async/Awaitとマルチスレッド
質問6:async/awaitとTaskの仕組みを説明してください
非同期プログラミングは現代のアプリケーションに不可欠です。内部動作の理解は、上級レベルの技術力を示します。
// 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 { }コンパイラはasyncメソッドをステートマシンに変換します。各awaitはスレッドが解放される中断ポイントを表します。
質問7:async/awaitでデッドロックを防ぐにはどうすればよいですか?
非同期デッドロックは、SynchronizationContextを持つアプリケーション(UI、従来のASP.NET)で発生しやすい典型的な落とし穴です。
// 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(非同期を最後まで貫く)」です。同期コードと非同期コードを混在させてはいけません。ASP.NET CoreではSynchronizationContextが存在しないため、デッドロックのリスクは軽減されています。
.NETの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
依存性注入とアーキテクチャ
質問8:DIのライフタイム(Scoped、Transient、Singleton)を説明してください
ライフタイムの理解は、並行性のバグやメモリリークを防ぐために不可欠です。
// 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...
}
}原則として、サービスは自身より短いライフタイムのサービスに依存してはいけません。Singleton -> Scoped -> Transientの順に長くなります。
質問9:.NETで主要な設計パターンは何ですか?
面接官は、定義の暗記ではなく、パターンの実践的な活用能力を求めています。
// 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;
}
}これらのパターンはプロフェッショナルな.NETアプリケーションで日常的に使用されます。Repository + Unit of Workパターンは、Entity Frameworkとの組み合わせで特に一般的です。
Entity Framework Core
質問10:EF Coreでパフォーマンスを最適化するにはどうすればよいですか?
EF Coreは使い方によって非常に高速にも遅くもなります。この質問はベストプラクティスの知識を評価します。
// 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);
}
}開発環境ではoptionsBuilder.LogTo(Console.WriteLine)でSQLログを有効にし、問題のあるクエリを特定します。本番環境ではMiniProfilerやApplication Insightsなどのツールを使用します。
質問11:マイグレーションとスキーマ管理について説明してください
マイグレーション管理は、本番環境へのデプロイにおいて極めて重要です。
// 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" }
);
}
}主要なマイグレーションコマンドは以下の通りです。
dotnet ef migrations add MigrationName- マイグレーションの作成dotnet ef database update- マイグレーションの適用dotnet ef migrations script- SQLスクリプトの生成dotnet ef migrations remove- 最後のマイグレーションの削除
ASP.NET Core
質問12:ASP.NET Coreのミドルウェアパイプラインを説明してください
ミドルウェアパイプラインはASP.NET Coreの中核です。その動作を理解することは必須事項となります。
// 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>()
);
}
}ミドルウェアはリクエスト時に登録順で実行され、レスポンス時に逆順で実行されます。
質問13:JWT認証をどのように実装しますか?
JWT認証は、現代のREST APIにおける標準的な認証方式です。
// 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>とは何ですか?
これらの型は、メモリ割り当てなしにメモリ操作を可能にし、高性能コードに不可欠です。
// 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>は、文字列処理、パース、配列操作をメモリ割り当てなしで行う際に最適です。
質問15:recordとその使い所を説明してください
record(C# 9以降)は、値ベースの等価性を持つ不変の参照型で、DTOやValue Objectに最適です。
// 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、不変の設定値、そして参照ではなく値によってアイデンティティが決まるあらゆるオブジェクトです。
質問16:分散キャッシュシステムをどのように実装しますか?
キャッシュは大規模アプリケーションのパフォーマンスに不可欠です。
// 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>();
}
}「コンピュータサイエンスで難しいことは2つだけ。キャッシュの無効化と命名である。」古いデータを避けるために、明確なキャッシュ無効化戦略の策定が不可欠です。
質問17:分散トランザクションをどのように扱いますか?
マイクロサービスアーキテクチャでは、分散トランザクションに固有のパターンが必要です。
// 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パターンは、分散システムにおける結果整合性を保証します。Outboxパターンは、障害発生時でも確実にイベントを発行する仕組みを提供します。
まとめ
C#と.NETの技術面接では、ランタイムと言語に関する理論的知識と、アーキテクチャおよびアプリケーション開発における実践的スキルの両方が評価されます。基礎的な概念を深く理解しつつ、高度なパターンにも精通していることが、シニア開発者としての差別化につながります。
準備チェックリスト
- 値型と参照型の違いを理解する
- async/awaitを習得し、デッドロックを回避する
- IEnumerableとIQueryableの違いを把握する
- Entity Framework Coreのクエリを最適化する
- IDisposableパターンを正しく実装する
- 適切なライフタイムで依存性注入を構成する
- JWTによるAPI認証を実装する
- Span
<T>とMemory<T>を使った高性能コードを書く
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
面接対策には理論と実践の両立が求められます。個人プロジェクトの構築、.NETオープンソースへの貢献、HackerRankやLeetCodeでの演習を通じて知識を定着させることが、高難度の面接を突破する鍵となります。
タグ
共有
関連記事

.NET 8: ASP.NET CoreでAPIを構築する
.NET 8とASP.NET Coreを使用したプロフェッショナルなREST APIの構築に関する完全ガイド。コントローラー、Entity Framework Core、バリデーション、ベストプラクティスを解説。

LaravelとPHPの面接質問:2026年版トップ25
LaravelとPHPの面接で最も頻出する25の質問を解説します。Eloquent ORM、ミドルウェア、キュー、テスト、アーキテクチャパターンについて、詳細な回答とコード例を掲載しています。

DjangoとPythonの面接質問:2026年版トップ25
DjangoとPythonの面接で最も頻出する25の質問を解説します。ORM、ビュー、ミドルウェア、Django REST Framework、シグナル、パフォーマンス最適化について、詳細な回答とコード例を掲載しています。