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