# Domande Colloquio C# e .NET: Guida Completa 2026 > Le 17 domande più frequenti nei colloqui C# e .NET. LINQ, async/await, dependency injection, Entity Framework e best practice con risposte dettagliate ed esempi di codice. - Published: 2026-01-25 - Updated: 2026-04-10 - Author: SharpSkill - Tags: csharp, dotnet, interview, aspnet core, technical interview - Reading time: 20 min --- I colloqui C# e .NET valutano la padronanza del linguaggio, la comprensione dell'ecosistema Microsoft e la capacità di progettare applicazioni robuste e performanti. Questa guida copre le domande essenziali, dai fondamenti del linguaggio ai pattern architetturali avanzati. > **Suggerimento per il Colloquio** > > I recruiter apprezzano risposte che dimostrano la comprensione dei meccanismi interni di .NET, non solo della sintassi. Spiegare il "perché" dietro ogni concetto fa tutta la differenza. ## Fondamenti di C# ### Domanda 1: Qual è la differenza tra value type e reference type? Questa distinzione fondamentale influisce sull'allocazione della memoria, sulle prestazioni e sul comportamento nel passaggio dei parametri. ```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 } } ``` I value type (int, struct, enum) vengono allocati sullo Stack e liberati automaticamente. I reference type (class, interface, delegate) vengono allocati sull'Heap e gestiti dal Garbage Collector. ### Domanda 2: Spiegare le keyword `ref`, `out` e `in` Questi modificatori controllano il modo in cui i parametri vengono passati ai metodi, con implicazioni per le prestazioni e la mutabilità. ```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` è particolarmente utile per struct di grandi dimensioni perché evita la copia garantendo al contempo l'immutabilità. Si tratta di un pattern comune nel codice ad alte prestazioni. > **Performance con in** > > L'uso di `in` per struct superiori a 16 byte migliora le prestazioni evitando le copie. Per struct piccole, il passaggio per valore resta più efficiente. ### Domanda 3: Come funziona il Garbage Collector in .NET? Il GC di .NET utilizza un algoritmo generazionale per ottimizzare la gestione automatica della memoria. ```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 } } ``` Il GC raccoglie la Generazione 0 frequentemente (millisecondi), la Generazione 1 occasionalmente e la Generazione 2 raramente. Gli oggetti LOH (Large Object Heap > 85KB) vengono gestiti separatamente. ## LINQ e Collezioni ### Domanda 4: Qual è la differenza tra IEnumerable e IQueryable? Questa domanda è cruciale per comprendere l'esecuzione differita e le prestazioni delle query. ```csharp // EnumerableVsQueryable.cs // Fundamental execution differences public class LinqDemo { public static void CompareExecution(AppDbContext context) { // IEnumerable: executes IN MEMORY (client-side) IEnumerable enumerable = context.Products.AsEnumerable(); var filteredEnum = enumerable .Where(p => p.Price > 100) // Filtering in C# .ToList(); // Generated SQL: SELECT * FROM Products (ALL loaded) // IQueryable: executes on DATABASE (server-side) IQueryable queryable = context.Products; var filteredQuery = queryable .Where(p => p.Price > 100) // Translated to SQL WHERE .ToList(); // Generated SQL: SELECT * FROM Products WHERE Price > 100 // Query composition with IQueryable var query = context.Products.AsQueryable(); // Each operation adds to the Expression Tree query = query.Where(p => p.IsActive); query = query.Where(p => p.CategoryId == 5); query = query.OrderBy(p => p.Name); // Execution happens HERE, with a single optimized SQL query var results = query.ToList(); } // Generic method that works with both public static IEnumerable FilterByCondition( IEnumerable source, Func predicate) { return source.Where(predicate); } // Optimized version for IQueryable public static IQueryable FilterByCondition( IQueryable source, Expression> predicate) { // Expression> enables SQL translation return source.Where(predicate); } } ``` Utilizzare `IQueryable` con Entity Framework per far eseguire il filtraggio lato database. `IEnumerable` è adatto per collezioni in memoria o quando tutti i dati sono già caricati. ### Domanda 5: Spiegare l'esecuzione differita in LINQ L'esecuzione differita è un concetto fondamentale che influisce sulle prestazioni e sul comportamento delle query. ```csharp // DeferredExecution.cs // Understanding when queries actually execute public class DeferredExecutionDemo { public static void Demonstrate() { var numbers = new List { 1, 2, 3, 4, 5 }; // Query is DEFINED but NOT EXECUTED var query = numbers.Where(n => { Console.WriteLine($"Evaluating {n}"); return n > 2; }); Console.WriteLine("Query defined, but nothing happened yet"); // Modifying source BEFORE execution numbers.Add(6); numbers.Add(7); Console.WriteLine("Starting iteration:"); // EXECUTION happens HERE during enumeration foreach (var n in query) { Console.WriteLine($"Result: {n}"); } // Output includes 6 and 7 because they were added before execution } // Methods that FORCE immediate execution public static void ImmediateExecution() { var numbers = new List { 1, 2, 3, 4, 5 }; // ToList(), ToArray(), ToDictionary() = immediate execution var list = numbers.Where(n => n > 2).ToList(); // Count(), First(), Single(), Any() = immediate execution var count = numbers.Where(n => n > 2).Count(); var first = numbers.First(n => n > 2); // Aggregate(), Sum(), Max(), Min() = immediate execution var sum = numbers.Where(n => n > 2).Sum(); } // Danger: multiple enumeration public static void MultipleEnumerationProblem() { var numbers = GetNumbers(); // IEnumerable returned by yield // WARNING: EACH use re-executes the query var count = numbers.Count(); // 1st enumeration var first = numbers.First(); // 2nd enumeration // SOLUTION: materialize once var materializedList = numbers.ToList(); var countOk = materializedList.Count; // No re-execution var firstOk = materializedList.First(); // No re-execution } private static IEnumerable GetNumbers() { Console.WriteLine("GetNumbers called"); yield return 1; yield return 2; yield return 3; } } ``` > **Enumerazione Multipla** > > Utilizzare un analizzatore come ReSharper o Rider per rilevare problemi di enumerazione multipla che possono causare bug sottili e problemi di prestazioni. ## Async/Await e Multithreading ### Domanda 6: Spiegare async/await e il funzionamento dei Task La programmazione asincrona è essenziale per le applicazioni moderne. Comprenderne i meccanismi interni dimostra una competenza avanzata. ```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 { } ``` Il compilatore trasforma i metodi async in macchine a stati. Ogni `await` rappresenta un punto di sospensione in cui il thread viene rilasciato. ### Domanda 7: Come evitare i deadlock con async/await? I deadlock asincroni sono una trappola classica, specialmente nelle applicazioni con SynchronizationContext (UI, ASP.NET classico). ```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(); } ``` La regola d'oro: "async all the way". Evitare di mescolare codice sincrono e asincrono. In ASP.NET Core il SynchronizationContext non esiste, riducendo il rischio di deadlock. ## Dependency Injection e Architettura ### Domanda 8: Spiegare i diversi lifetime della DI (Scoped, Transient, Singleton) Comprendere i lifetime è essenziale per evitare bug di concorrenza e memory leak. ```csharp // DependencyInjectionLifetimes.cs // The three lifetimes and their implications // SINGLETON: single instance for the entire application public class SingletonService { private readonly Guid _id = Guid.NewGuid(); public Guid Id => _id; // DANGER: no mutable state without synchronization // private int _counter; // Possible race conditions } // SCOPED: one instance per HTTP request (or scope) public class ScopedService { private readonly Guid _id = Guid.NewGuid(); public Guid Id => _id; // Safe: each request has its own instance // Ideal for DbContext, UnitOfWork } // TRANSIENT: new instance on every injection public class TransientService { private readonly Guid _id = Guid.NewGuid(); public Guid Id => _id; // Ideal for lightweight, stateless services } // Configuration in Program.cs public static class ServiceConfiguration { public static void ConfigureServices(IServiceCollection services) { services.AddSingleton(); services.AddScoped(); services.AddTransient(); // Entity Framework: ALWAYS Scoped services.AddDbContext(options => options.UseSqlServer(connectionString)); // HttpClient: use IHttpClientFactory services.AddHttpClient(); } } // CAPTIVE DEPENDENCY: Singleton depending on Scoped public class BadSingletonService { // WARNING: ScopedService will be captured and reused indefinitely // Causes concurrency bugs and stale data private readonly ScopedService _scoped; public BadSingletonService(ScopedService scoped) { _scoped = scoped; } } // SOLUTION: use IServiceScopeFactory public class GoodSingletonService { private readonly IServiceScopeFactory _scopeFactory; public GoodSingletonService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public async Task DoWork() { // Create explicit scope to get fresh ScopedService using var scope = _scopeFactory.CreateScope(); var scoped = scope.ServiceProvider.GetRequiredService(); // Use scoped... } } ``` Regola: un servizio non dovrebbe mai dipendere da un servizio con un lifetime più breve. Singleton -> Scoped -> Transient. ### Domanda 9: Quali sono i principali design pattern in .NET? I recruiter si aspettano una conoscenza pratica dei pattern, non solo definizioni teoriche. ```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; } } ``` Questi pattern vengono utilizzati quotidianamente nelle applicazioni .NET professionali. Il pattern Repository con Unit of Work è particolarmente comune con Entity Framework. ## Entity Framework Core ### Domanda 10: Come ottimizzare le prestazioni con EF Core? EF Core può essere molto veloce o molto lento a seconda dell'utilizzo. Questa domanda valuta la conoscenza delle best practice. ```csharp // EFCoreOptimization.cs // Query optimization techniques public class EFCorePerformance { private readonly AppDbContext _context; // N+1 problem: one query per order public async Task> GetUsersWithOrdersBad() { var users = await _context.Users.ToListAsync(); foreach (var user in users) { // N additional queries! var orders = await _context.Orders .Where(o => o.UserId == user.Id) .ToListAsync(); } return users; } // Eager Loading with Include public async Task> GetUsersWithOrdersGood() { return await _context.Users .Include(u => u.Orders) // SQL JOIN .ThenInclude(o => o.Products) // Nested include .ToListAsync(); } // Projection to load only necessary data public async Task> GetUserSummaries() { return await _context.Users .Select(u => new UserDto { Id = u.Id, Name = u.Name, OrderCount = u.Orders.Count, // Calculated SQL-side TotalSpent = u.Orders.Sum(o => o.Total) }) .ToListAsync(); } // Split Query for large collections public async Task> GetUsersWithSplitQuery() { return await _context.Users .Include(u => u.Orders) .AsSplitQuery() // Generates separate queries instead of large JOIN .ToListAsync(); } // No Tracking for read-only operations public async Task> GetUsersReadOnly() { return await _context.Users .AsNoTracking() // No change tracking = faster .ToListAsync(); } // Batch operations (EF Core 7+) public async Task DeleteInactiveUsers() { // Single DELETE query instead of load then delete await _context.Users .Where(u => !u.IsActive && u.LastLoginAt < DateTime.UtcNow.AddYears(-1)) .ExecuteDeleteAsync(); } // Bulk update public async Task DeactivateOldUsers() { await _context.Users .Where(u => u.LastLoginAt < DateTime.UtcNow.AddMonths(-6)) .ExecuteUpdateAsync(u => u.SetProperty(x => x.IsActive, false)); } // Compiled Queries for frequent queries private static readonly Func> GetUserById = EF.CompileAsyncQuery((AppDbContext ctx, int id) => ctx.Users.FirstOrDefault(u => u.Id == id)); public async Task GetUserOptimized(int id) { return await GetUserById(_context, id); } } ``` > **Monitoraggio delle Query** > > Abilitare il logging SQL in sviluppo con `optionsBuilder.LogTo(Console.WriteLine)` per identificare le query problematiche. In produzione, utilizzare strumenti come MiniProfiler o Application Insights. ### Domanda 11: Spiegare le migration e la gestione dello schema La gestione delle migration è fondamentale per i deployment in produzione. ```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" } ); } } ``` Comandi essenziali per le migration: - `dotnet ef migrations add MigrationName` - Creare una migration - `dotnet ef database update` - Applicare le migration - `dotnet ef migrations script` - Generare uno script SQL - `dotnet ef migrations remove` - Rimuovere l'ultima migration ## ASP.NET Core ### Domanda 12: Spiegare la pipeline middleware di ASP.NET Core La pipeline middleware è il cuore di ASP.NET Core. Comprenderne il funzionamento è essenziale. ```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() ); } } ``` I middleware vengono eseguiti nell'ordine di registrazione in entrata (request) e in ordine inverso in uscita (response). ### Domanda 13: Come implementare l'autenticazione JWT? L'autenticazione JWT è lo standard per le API REST moderne. ```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!" }); } } ``` ## Domande Avanzate ### Domanda 14: Cosa sono Span`` e Memory``? Questi tipi permettono la manipolazione della memoria senza allocazioni, essenziale per il codice ad alte prestazioni. ```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`` è ideale per l'elaborazione di stringhe, il parsing e le operazioni su array senza allocazioni. ### Domanda 15: Spiegare i record e i loro casi d'uso I record (C# 9+) sono un tipo riferimento immutabile con uguaglianza basata sui valori, perfetti per DTO e 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); } ``` I record sono ideali per: DTO, Value Object, configurazioni immutabili e qualsiasi oggetto la cui identità si basa sui valori anziché sul riferimento. ### Domanda 16: Come implementare un sistema di cache distribuita? Il caching è essenziale per le prestazioni delle applicazioni su larga scala. ```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(); } } ``` > **Invalidazione della Cache** > > "Ci sono solo due cose difficili in informatica: l'invalidazione della cache e dare nomi alle cose." Definire una strategia chiara di invalidazione della cache è essenziale per evitare dati obsoleti. ### Domanda 17: Come gestire le transazioni distribuite? Nelle architetture a microservizi, le transazioni distribuite richiedono pattern specifici. ```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); } } ``` Il pattern SAGA garantisce la consistenza eventuale nei sistemi distribuiti. Il pattern Outbox assicura la pubblicazione affidabile degli eventi anche in caso di guasti. ## Conclusione I colloqui C# e .NET valutano una combinazione di conoscenza teorica sul runtime e sul linguaggio, e competenze pratiche in architettura e sviluppo applicativo. Padroneggiare i concetti fondamentali e al contempo comprendere i pattern avanzati distingue gli sviluppatori senior. ### Checklist di Preparazione - Comprendere la differenza tra value type e reference type - Padroneggiare async/await ed evitare i deadlock - Conoscere le differenze tra IEnumerable e IQueryable - Ottimizzare le query di Entity Framework Core - Implementare correttamente il pattern IDisposable - Configurare la dependency injection con i lifetime appropriati - Proteggere le API con JWT - Utilizzare Span`` e Memory`` per il codice ad alte prestazioni La preparazione dovrebbe combinare teoria e pratica. Costruire progetti personali, contribuire all'ecosistema open source di .NET e risolvere esercizi su piattaforme come HackerRank o LeetCode consolida queste conoscenze per affrontare i colloqui più impegnativi. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/dotnet/csharp-dotnet-interview-questions